#!/usr/bin/env python3
"""
ResolutionFlow Decision Trees - Batch 3: Microsoft 365

Six M365 troubleshooting trees for MSP engineers.
Imported by seed_trees_v2.py for seeding.

Trees:
1. Teams Call Quality Issues
2. OneDrive Sync Problems
3. Mail Flow Issues (Exchange Online)
4. SharePoint Permissions Problems
5. MFA / Conditional Access Lockout
6. License Assignment Problems
"""

from typing import Any


# =============================================================================
# Tree 1: Teams Call Quality Issues
# =============================================================================
def get_teams_call_quality_tree() -> dict[str, Any]:
    """Teams Call Quality Issues - M365 tree."""
    return {
        "name": "Teams Call Quality Issues",
        "description": "Diagnose and resolve Microsoft Teams call quality problems including choppy audio, dropped calls, video freezing, and echo. Covers network diagnostics, client troubleshooting, and Teams admin center analysis.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What type of call quality issue is the user experiencing?",
            "help_text": "Get specifics from the user. Different symptoms point to different root causes.",
            "options": [
                {"id": "audio_choppy", "label": "Choppy/robotic audio or audio cutting out", "next_node_id": "check_network_basics"},
                {"id": "calls_dropping", "label": "Calls disconnecting / dropping entirely", "next_node_id": "check_call_drop_scope"},
                {"id": "video_issues", "label": "Video freezing, pixelated, or not loading", "next_node_id": "check_video_bandwidth"},
                {"id": "echo_feedback", "label": "Echo, feedback, or background noise", "next_node_id": "check_audio_device"},
                {"id": "cant_join", "label": "Can't join calls at all", "next_node_id": "check_join_failure"}
            ],
            "children": [
                {
                    "id": "check_network_basics",
                    "type": "action",
                    "title": "Run Network Quality Checks",
                    "description": "Choppy audio is almost always network-related. Run these checks:\n\n**Step 1: Speed test**\nGo to https://www.speedtest.net — Teams needs:\n- 1.5 Mbps up/down minimum for calls\n- 4 Mbps+ for video calls\n- Jitter under 30ms, latency under 50ms\n\n**Step 2: Check Teams Network Assessment Tool**\n```\n# Download from Microsoft, run:\nNetworkAssessmentTool.exe\n```\n\n**Step 3: Quick checks:**\n- Is the user on Wi-Fi or Ethernet?\n- Is anyone else on the network having issues?\n- Is the user on VPN? (common cause)\n\n**Document:** Speed test results, Wi-Fi vs wired, VPN status.",
                    "next_node_id": "network_result"
                },
                {
                    "id": "network_result",
                    "type": "decision",
                    "question": "What did the network check reveal?",
                    "help_text": "Compare results against Teams requirements",
                    "options": [
                        {"id": "low_bandwidth", "label": "Bandwidth is low (under 4 Mbps)", "next_node_id": "fix_bandwidth"},
                        {"id": "high_jitter", "label": "High jitter (>30ms) or packet loss", "next_node_id": "fix_jitter"},
                        {"id": "vpn_issue", "label": "User is on VPN — likely the cause", "next_node_id": "fix_vpn_teams"},
                        {"id": "network_ok", "label": "Network looks fine", "next_node_id": "check_teams_client"}
                    ],
                    "children": [
                        {
                            "id": "fix_bandwidth",
                            "type": "action",
                            "title": "Address Low Bandwidth",
                            "description": "Bandwidth is insufficient for quality Teams calls.\n\n**Immediate fixes:**\n- Switch to wired Ethernet if on Wi-Fi\n- Close bandwidth-heavy apps (streaming, large downloads)\n- Turn off incoming video in the call to save bandwidth\n- Ask others on the network to pause heavy usage during calls\n\n**If on Wi-Fi:**\n- Move closer to the access point\n- Switch to 5GHz band if available\n- Check for interference (microwaves, Bluetooth devices nearby)\n\n**If bandwidth is consistently low:** This is an ISP or network infrastructure issue. Escalate to the client's network admin or ISP.",
                            "next_node_id": "bandwidth_resolved"
                        },
                        {
                            "id": "bandwidth_resolved",
                            "type": "decision",
                            "question": "Did the bandwidth fixes help?",
                            "help_text": "Have the user try another call after making changes",
                            "options": [
                                {"id": "yes", "label": "Yes, call quality improved", "next_node_id": "solution_bandwidth"},
                                {"id": "no", "label": "No, still having issues", "next_node_id": "check_teams_client"}
                            ],
                            "children": [
                                {
                                    "id": "solution_bandwidth",
                                    "type": "solution",
                                    "title": "Resolved: Bandwidth Issue",
                                    "description": "Call quality improved after addressing bandwidth.\n\n**Ticket Notes:** Teams call quality issue caused by insufficient bandwidth. Resolved by [switching to Ethernet / closing competing apps / adjusting Wi-Fi].\n\n**Recommendations for client:**\n- Prioritize Teams traffic via QoS if they have a managed network\n- Consider dedicated bandwidth for voice/video if this is recurring\n- Teams audio ports: UDP 3478-3481"
                                }
                            ]
                        },
                        {
                            "id": "fix_jitter",
                            "type": "action",
                            "title": "Address High Jitter / Packet Loss",
                            "description": "High jitter or packet loss causes choppy, robotic audio.\n\n**Check for the cause:**\n```\n# Continuous ping to check for loss\nping -t teams.microsoft.com\n\n# Trace route to find where loss occurs\ntracert teams.microsoft.com\n```\n\n**Common causes:**\n- Congested Wi-Fi (too many devices on same AP)\n- Bad Ethernet cable or port\n- ISP congestion (especially cable internet during peak hours)\n- Old/overloaded router or switch\n- Network loop or broadcast storm\n\n**If packet loss is at the first hop:** Local network issue (switch, cable, or Wi-Fi AP).\n**If packet loss starts mid-route:** ISP or upstream issue.",
                            "next_node_id": "jitter_resolved"
                        },
                        {
                            "id": "jitter_resolved",
                            "type": "decision",
                            "question": "Were you able to reduce jitter/packet loss?",
                            "help_text": "Rerun speed test to verify improvement",
                            "options": [
                                {"id": "yes", "label": "Yes, jitter/loss improved", "next_node_id": "solution_jitter"},
                                {"id": "no", "label": "No, network still unstable", "next_node_id": "solution_escalate_network"}
                            ],
                            "children": [
                                {
                                    "id": "solution_jitter",
                                    "type": "solution",
                                    "title": "Resolved: Network Jitter / Packet Loss",
                                    "description": "Call quality improved after fixing network jitter.\n\n**Ticket Notes:** Teams call quality issue caused by network jitter/packet loss. Root cause: [Wi-Fi congestion / bad cable / ISP issue]. Resolved by [specific fix].\n\n**Prevention:** Implement QoS policies to prioritize real-time media traffic. Teams uses UDP 3478-3481 for media."
                                },
                                {
                                    "id": "solution_escalate_network",
                                    "type": "solution",
                                    "title": "Escalate: Network Infrastructure Issue",
                                    "description": "Network issues persist — escalate to network team or ISP.\n\n**Ticket Notes:** Teams call quality degraded due to persistent jitter/packet loss. Local troubleshooting performed. Traceroute shows loss at [hop]. Escalating to [network team / ISP].\n\n**Include in escalation:**\n- Speed test results\n- Traceroute output\n- Times when issues are worst\n- Number of affected users"
                                }
                            ]
                        },
                        {
                            "id": "fix_vpn_teams",
                            "type": "action",
                            "title": "Resolve VPN Impact on Teams",
                            "description": "VPN is a very common cause of Teams call quality issues. VPN tunnels add latency and can't handle real-time media well.\n\n**Best fix: Split tunnel Teams traffic**\nTeams should bypass the VPN. Most modern VPN clients support split tunneling.\n\n**Microsoft's M365 optimize endpoints that should bypass VPN:**\n- Teams media: UDP 3478-3481 to 13.107.64.0/18, 52.112.0.0/14\n- Check: https://aka.ms/o365endpoints\n\n**If split tunneling isn't an option:**\n- Disconnect VPN for the duration of the call\n- Use Teams on a phone (off VPN) as a fallback\n\n**Verify improvement:** Have the user make a test call with VPN disconnected. If quality improves, VPN is confirmed as the cause.",
                            "next_node_id": "solution_vpn_teams"
                        },
                        {
                            "id": "solution_vpn_teams",
                            "type": "solution",
                            "title": "Resolved: VPN Causing Teams Quality Issues",
                            "description": "VPN tunnel was degrading Teams call quality.\n\n**Ticket Notes:** Teams call quality issue caused by VPN tunnel routing real-time media traffic. Recommended split tunneling for M365 optimize endpoints.\n\n**Recommendation to client:** Implement split tunneling for Microsoft 365 'Optimize' category endpoints. This is Microsoft's official recommendation and significantly improves Teams performance."
                        },
                        {
                            "id": "check_teams_client",
                            "type": "action",
                            "title": "Check Teams Client and Device",
                            "description": "Network looks fine — check the Teams client itself.\n\n**Step 1: Check Teams version**\nClick the three dots (···) > About > Check for updates\nMake sure it's the new Teams (not classic Teams).\n\n**Step 2: Clear Teams cache**\n```\n# Close Teams first, then:\nrd /s /q \"%APPDATA%\\Microsoft\\Teams\"\n# For new Teams:\nrd /s /q \"%LOCALAPPDATA%\\Packages\\MSTeams_8wekyb3d8bbwe\\LocalCache\"\n```\n\n**Step 3: Check audio device**\n- Settings > Devices — is the correct mic/speaker selected?\n- Make a test call: Settings > Devices > Make a test call\n\n**Step 4: Check for GPU/driver issues**\n- Settings > General > disable GPU hardware acceleration\n- Update audio and GPU drivers",
                            "next_node_id": "client_fix_result"
                        },
                        {
                            "id": "client_fix_result",
                            "type": "decision",
                            "question": "Did client-side fixes help?",
                            "help_text": "Have the user make a test call after each change",
                            "options": [
                                {"id": "yes", "label": "Yes, quality improved", "next_node_id": "solution_client_fix"},
                                {"id": "no", "label": "No, still having issues", "next_node_id": "check_admin_center"}
                            ],
                            "children": [
                                {
                                    "id": "solution_client_fix",
                                    "type": "solution",
                                    "title": "Resolved: Teams Client Issue",
                                    "description": "Call quality improved after Teams client troubleshooting.\n\n**Ticket Notes:** Teams call quality issue resolved by [updating Teams / clearing cache / fixing audio device / disabling GPU acceleration].\n\n**Follow-up:** Monitor for recurrence. If the issue was a corrupt cache, it's unlikely to recur."
                                },
                                {
                                    "id": "check_admin_center",
                                    "type": "action",
                                    "title": "Check Teams Admin Center Call Analytics",
                                    "description": "Use the Teams Admin Center for detailed call diagnostics.\n\n**Teams Admin Center > Users > [Select User] > Call history**\n- Look at recent calls for quality scores\n- Check the 'Advanced' tab for packet loss, jitter, and round-trip time\n\n**Call Quality Dashboard (CQD):**\nhttps://cqd.teams.microsoft.com\n- Shows trends across the organization\n- Filter by user, building, subnet\n\n**What to look for:**\n- Poor streams marked in red\n- High packet loss (>5%)\n- High jitter (>30ms)\n- Audio device issues flagged\n\n**If issue is org-wide:** Likely a network/ISP issue at the office. Escalate.",
                                    "next_node_id": "solution_escalate_teams"
                                },
                                {
                                    "id": "solution_escalate_teams",
                                    "type": "solution",
                                    "title": "Escalate: Teams Call Quality — Admin Investigation",
                                    "description": "Individual troubleshooting didn't resolve the issue. Escalation needed.\n\n**Ticket Notes:** Teams call quality issues persist after network and client troubleshooting. Call analytics reviewed in Teams Admin Center. [Include CQD findings.]\n\n**Possible next steps:**\n- Open a Microsoft support ticket with call IDs\n- Review network architecture for QoS/traffic prioritization\n- Consider deploying Teams Network Assessment Tool org-wide\n- Check if issue correlates with specific ISP, building, or subnet"
                                }
                            ]
                        }
                    ]
                },
                {
                    "id": "check_call_drop_scope",
                    "type": "decision",
                    "question": "Is it just this one user or multiple users dropping calls?",
                    "help_text": "Scope determines whether this is a local issue or org-wide",
                    "options": [
                        {"id": "one_user", "label": "Just this one user", "next_node_id": "check_network_basics"},
                        {"id": "multiple", "label": "Multiple users at the same location", "next_node_id": "solution_escalate_network"},
                        {"id": "org_wide", "label": "Happening across the org / multiple locations", "next_node_id": "check_m365_health"}
                    ],
                    "children": [
                        {
                            "id": "check_m365_health",
                            "type": "action",
                            "title": "Check Microsoft 365 Service Health",
                            "description": "Org-wide call drops may be a Microsoft service issue.\n\n**Check service health:**\n1. Microsoft 365 Admin Center > Health > Service health\n2. Look for advisories on Microsoft Teams\n3. Check https://status.office365.com\n4. Check @MSABORSKY on Twitter/X for outage reports\n\n**Also check:** https://downdetector.com/status/ms-teams/\n\n**If there's an active incident:** Microsoft is aware. Document the incident ID and wait for resolution. Set expectations with users.",
                            "next_node_id": "service_health_result"
                        },
                        {
                            "id": "service_health_result",
                            "type": "decision",
                            "question": "Is there a Microsoft service issue?",
                            "help_text": "Check M365 Admin Center service health",
                            "options": [
                                {"id": "yes_outage", "label": "Yes, active Teams incident", "next_node_id": "solution_m365_outage"},
                                {"id": "no_outage", "label": "No active incidents", "next_node_id": "solution_escalate_teams"}
                            ],
                            "children": [
                                {
                                    "id": "solution_m365_outage",
                                    "type": "solution",
                                    "title": "Microsoft 365 Service Incident",
                                    "description": "Active Microsoft service incident is causing Teams call drops.\n\n**Ticket Notes:** Teams call drops affecting multiple users are caused by Microsoft service incident [Incident ID]. Microsoft is investigating. No action required on our end.\n\n**Communication to users:**\n- Microsoft is aware of the issue and working on a fix\n- Workaround: Use phone dial-in for critical meetings\n- Monitor M365 Admin Center for updates\n\n**Follow-up:** Verify resolution once Microsoft marks the incident as resolved."
                                }
                            ]
                        }
                    ]
                },
                {
                    "id": "check_video_bandwidth",
                    "type": "action",
                    "title": "Check Video Bandwidth and Hardware",
                    "description": "Video issues need more bandwidth and GPU than audio.\n\n**Teams video requirements:**\n- 1:1 video: 1.5 Mbps up/down\n- Group video: 2.5 Mbps up/down\n- HD video: 4+ Mbps\n\n**Quick checks:**\n1. Run speed test — is bandwidth sufficient?\n2. Is the user on Wi-Fi? (5GHz recommended for video)\n3. Check CPU usage during a call — is it over 80%?\n4. Is the laptop plugged in? (throttles on battery)\n\n**Try:** Have the user turn off incoming video to see if their own video stabilizes. If it does, it's a bandwidth issue.\n\n**GPU acceleration:** Settings > General — toggle hardware acceleration.",
                    "next_node_id": "network_result"
                },
                {
                    "id": "check_audio_device",
                    "type": "action",
                    "title": "Troubleshoot Echo / Audio Feedback",
                    "description": "Echo and feedback are device issues, not network issues.\n\n**Common causes:**\n- Speaker audio being picked up by the microphone\n- Using laptop speakers + laptop mic (not headset)\n- Two devices in the same room both joined to the call\n- Bluetooth headset with poor echo cancellation\n\n**Fixes:**\n1. **Use a headset.** This is the #1 fix for echo.\n2. Check if user has multiple devices in the call (phone + laptop)\n3. In Teams: Settings > Devices — enable noise suppression (High)\n4. Lower speaker volume to reduce mic pickup\n5. If using a conference room speaker: check for echo cancellation settings\n\n**Test:** Settings > Devices > Make a test call",
                    "next_node_id": "echo_resolved"
                },
                {
                    "id": "echo_resolved",
                    "type": "decision",
                    "question": "Did the echo/feedback improve?",
                    "help_text": "Make a test call after changes",
                    "options": [
                        {"id": "yes", "label": "Yes, audio is clean now", "next_node_id": "solution_echo_fix"},
                        {"id": "no", "label": "No, echo persists", "next_node_id": "check_teams_client"}
                    ],
                    "children": [
                        {
                            "id": "solution_echo_fix",
                            "type": "solution",
                            "title": "Resolved: Audio Echo / Feedback",
                            "description": "Echo resolved by fixing the audio device setup.\n\n**Ticket Notes:** Teams echo/feedback caused by [open speakers + mic / duplicate devices in call / no headset]. Resolved by [using headset / removing duplicate device / enabling noise suppression].\n\n**Recommendation:** Encourage headset use for all Teams calls, especially in shared office spaces."
                        }
                    ]
                },
                {
                    "id": "check_join_failure",
                    "type": "action",
                    "title": "Diagnose Call Join Failure",
                    "description": "User can't join Teams calls at all.\n\n**Step 1: Can they join from the web?**\nTry joining at https://teams.microsoft.com — this rules out client issues.\n\n**Step 2: Check firewall/proxy**\nTeams requires these to be open:\n- TCP 443 (HTTPS)\n- UDP 3478-3481 (media — CRITICAL)\n- IP ranges: 13.107.64.0/18, 52.112.0.0/14\n\n**Step 3: Check Teams permissions**\n- Is the user licensed for Teams?\n- Teams Admin Center > Users > check calling policies\n- Is Teams enabled in their M365 license?\n\n**Step 4: Is it a meeting-specific issue?**\n- Can they start their own meeting?\n- Can they join other meetings?\n- Is the meeting from an external org? (external access may be blocked)",
                    "next_node_id": "join_failure_result"
                },
                {
                    "id": "join_failure_result",
                    "type": "decision",
                    "question": "What resolved the join failure?",
                    "help_text": "Work through the steps above",
                    "options": [
                        {"id": "web_works", "label": "Web app works — client issue", "next_node_id": "check_teams_client"},
                        {"id": "firewall", "label": "Firewall/proxy was blocking Teams", "next_node_id": "solution_firewall_teams"},
                        {"id": "license", "label": "User wasn't licensed for Teams", "next_node_id": "solution_teams_license"},
                        {"id": "still_broken", "label": "Nothing works — can't join anywhere", "next_node_id": "check_m365_health"}
                    ],
                    "children": [
                        {
                            "id": "solution_firewall_teams",
                            "type": "solution",
                            "title": "Resolved: Firewall Blocking Teams",
                            "description": "Firewall or proxy was blocking Teams media traffic.\n\n**Ticket Notes:** Teams calls failing due to firewall blocking required ports. Opened UDP 3478-3481 for Teams media traffic.\n\n**Required endpoints for Teams:**\n- TCP 443 for signaling\n- UDP 3478-3481 for media\n- IPs: 13.107.64.0/18, 52.112.0.0/14, 52.120.0.0/14\n\n**Important:** If UDP is blocked, Teams falls back to TCP which significantly degrades call quality. UDP must be open for good call quality."
                        },
                        {
                            "id": "solution_teams_license",
                            "type": "solution",
                            "title": "Resolved: Teams License Missing",
                            "description": "User did not have a Teams license assigned.\n\n**Ticket Notes:** User unable to join Teams calls. M365 license did not include Teams (or Teams was disabled in the license). Assigned Teams license and waited for provisioning.\n\n**Note:** License changes can take up to 24 hours to fully propagate, though usually it's within an hour. User may need to sign out and back in."
                        }
                    ]
                }
            ]
        }
    }


# =============================================================================
# Tree 2: OneDrive Sync Problems
# =============================================================================
def get_onedrive_sync_tree() -> dict[str, Any]:
    """OneDrive Sync Problems - M365 tree."""
    return {
        "name": "OneDrive Sync Problems",
        "description": "Troubleshoot OneDrive sync failures, stuck syncs, conflict files, and storage issues. Covers the OneDrive sync client, Known Folder Move (KFM), SharePoint library sync, and common error codes.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What is the OneDrive sync status icon showing?",
            "help_text": "Look at the OneDrive cloud icon in the system tray (bottom right). Hover over it for status. The icon color and symbol indicate the issue type.",
            "options": [
                {"id": "red_x", "label": "Red X or red circle — sync error", "next_node_id": "check_sync_error"},
                {"id": "paused", "label": "Paused icon — sync is paused", "next_node_id": "check_paused_reason"},
                {"id": "stuck_processing", "label": "Sync arrows spinning but not completing (stuck)", "next_node_id": "check_stuck_sync"},
                {"id": "conflicts", "label": "Conflict files appearing (duplicates with names like 'file-username')", "next_node_id": "check_conflicts"},
                {"id": "no_icon", "label": "No OneDrive icon at all", "next_node_id": "check_onedrive_running"}
            ],
            "children": [
                {
                    "id": "check_sync_error",
                    "type": "action",
                    "title": "Identify the Sync Error",
                    "description": "Click the OneDrive icon > click the error message to see details.\n\n**Common errors and what they mean:**\n- **\"You're out of storage\"** → OneDrive is full (check quota)\n- **\"File is in use\"** → Close the file in the app that has it open\n- **\"File name contains invalid characters\"** → Rename the file (remove # % & { } \\ < > * ? / ! ' \" : @ + ` | =)\n- **\"Path is too long\"** → Total path must be under 400 characters\n- **\"This item might not exist or is no longer available\"** → File was deleted from server\n- **\"We can't sync this library\"** → SharePoint library issue\n\n**Get the specific error code:** OneDrive icon > Help & Settings > Settings > look for error codes.\n\n**Document:** The exact error message and code.",
                    "next_node_id": "sync_error_type"
                },
                {
                    "id": "sync_error_type",
                    "type": "decision",
                    "question": "What type of sync error is it?",
                    "help_text": "Based on the error message you found",
                    "options": [
                        {"id": "storage_full", "label": "Storage full / quota exceeded", "next_node_id": "fix_storage"},
                        {"id": "file_issue", "label": "Specific file(s) can't sync (name, size, locked)", "next_node_id": "fix_file_issues"},
                        {"id": "auth_error", "label": "Sign-in or authentication error", "next_node_id": "fix_onedrive_auth"},
                        {"id": "library_error", "label": "Can't sync SharePoint library", "next_node_id": "fix_sharepoint_sync"}
                    ],
                    "children": [
                        {
                            "id": "fix_storage",
                            "type": "action",
                            "title": "Resolve Storage Full Issue",
                            "description": "OneDrive storage is full.\n\n**Check current usage:**\n1. OneDrive icon > Help & Settings > Settings > Account tab\n2. Or go to https://onedrive.live.com > Settings > Storage\n\n**Default quotas:**\n- Business: 1 TB per user (can be increased to 5 TB by admin)\n- E3/E5: 5 TB+\n\n**Free up space:**\n1. Empty the OneDrive recycle bin (items count toward quota for 93 days)\n2. Use Files On-Demand: right-click large folders > Free up space\n3. Move large files that don't need to sync to a local drive\n\n**If user legitimately needs more space:** Admin can increase quota in M365 Admin Center > Users > OneDrive settings.",
                            "next_node_id": "solution_storage_fixed"
                        },
                        {
                            "id": "solution_storage_fixed",
                            "type": "solution",
                            "title": "Resolved: OneDrive Storage Issue",
                            "description": "OneDrive sync restored after resolving storage quota.\n\n**Ticket Notes:** OneDrive sync failed due to storage quota exceeded. Resolved by [emptying recycle bin / enabling Files On-Demand / increasing admin quota]. Current usage: [X] of [Y].\n\n**Prevention:** Enable Files On-Demand to keep storage usage low. Consider admin-side quota increases for power users."
                        },
                        {
                            "id": "fix_file_issues",
                            "type": "action",
                            "title": "Fix File-Specific Sync Errors",
                            "description": "Specific files can't sync. Fix based on the error:\n\n**Invalid characters:** Rename to remove: # % & { } \\ < > * ? / ! ' \" : @ + ` | =\n\n**Path too long (400 char limit):**\n- Shorten folder names in the path\n- Move the file to a less deeply nested location\n\n**File too large:** Max file size is 250 GB. Compress if possible.\n\n**File in use:** Close the file in whatever app has it open.\n```\n# Find what's locking a file:\nHandle.exe \"filename\" (from Sysinternals)\n```\n\n**Unsupported files:** OneNote notebooks sync separately. PST files and database files can't sync.\n\n**After fixing:** OneDrive should auto-retry. If not, pause and resume sync.",
                            "next_node_id": "solution_file_fix"
                        },
                        {
                            "id": "solution_file_fix",
                            "type": "solution",
                            "title": "Resolved: File Sync Error",
                            "description": "OneDrive sync error resolved by fixing the problematic file.\n\n**Ticket Notes:** OneDrive sync error on file [filename]. Cause: [invalid characters / path too long / file locked / file too large]. Resolved by [renaming / moving / closing / compressing].\n\n**Prevention:** Educate users on OneDrive file name and path limitations. Share the Microsoft article on restrictions."
                        },
                        {
                            "id": "fix_onedrive_auth",
                            "type": "action",
                            "title": "Fix OneDrive Authentication Error",
                            "description": "OneDrive can't authenticate to M365.\n\n**Step 1: Unlink and relink account**\nOneDrive icon > Settings > Account > Unlink this PC\nThen sign back in.\n\n**Step 2: Check credentials**\n- Can the user sign in at https://portal.office.com ?\n- Was their password recently changed?\n- Is their account enabled in Entra ID?\n\n**Step 3: Check Conditional Access**\n- Is the device compliant? (Intune)\n- Is OneDrive blocked by a Conditional Access policy?\n- Check sign-in logs in Entra ID > User > Sign-in logs\n\n**Step 4: Clear cached credentials**\n```\ncontrol keymgr.dll\n```\nRemove any entries for Office or Microsoft.",
                            "next_node_id": "solution_auth_fix"
                        },
                        {
                            "id": "solution_auth_fix",
                            "type": "solution",
                            "title": "Resolved: OneDrive Authentication Error",
                            "description": "OneDrive authentication restored.\n\n**Ticket Notes:** OneDrive sync failing due to authentication error. Resolved by [unlinking/relinking / clearing credentials / fixing Conditional Access compliance / password reset].\n\n**If Conditional Access was the issue:** Document the policy that blocked the sign-in and whether the device was non-compliant."
                        },
                        {
                            "id": "fix_sharepoint_sync",
                            "type": "action",
                            "title": "Fix SharePoint Library Sync Error",
                            "description": "SharePoint library won't sync to OneDrive.\n\n**Common causes:**\n- Library has more than 300,000 items (sync limit)\n- Sync was started by another user and permissions changed\n- Library uses checkout/approval workflows incompatible with sync\n\n**Fixes:**\n1. Stop syncing the library: OneDrive icon > Settings > Account > Stop sync\n2. Delete the local sync folder\n3. Re-sync from SharePoint: Go to the library in browser > Sync button\n\n**If \"library too large\":**\n- Sync specific folders instead of the entire library\n- SharePoint > Library > Sync > choose folders\n\n**If permissions changed:** Verify the user still has access to the library in SharePoint.",
                            "next_node_id": "solution_sp_sync_fix"
                        },
                        {
                            "id": "solution_sp_sync_fix",
                            "type": "solution",
                            "title": "Resolved: SharePoint Library Sync",
                            "description": "SharePoint library sync restored.\n\n**Ticket Notes:** SharePoint library sync failing. Resolved by [re-syncing / syncing specific folders / fixing permissions].\n\n**Best practice:** For large libraries, sync only the folders users actively need rather than the entire library."
                        }
                    ]
                },
                {
                    "id": "check_paused_reason",
                    "type": "action",
                    "title": "Check Why Sync is Paused",
                    "description": "OneDrive sync is paused. This can be manual or automatic.\n\n**Click the OneDrive icon** to see why:\n\n**Common reasons:**\n- User manually paused it (Resume button will be visible)\n- Battery saver mode paused sync (laptop on battery)\n- Metered network detected (mobile hotspot or flagged Wi-Fi)\n- Upload bandwidth limit set (Settings > Network)\n\n**To resume:**\n1. Click OneDrive icon > Resume syncing\n2. If on battery: plug in or change setting in OneDrive > Settings > General\n3. If metered: OneDrive > Settings > General > uncheck 'Pause when on metered'\n\n**If it keeps pausing itself:** Check if Group Policy is enforcing pause rules.",
                    "next_node_id": "solution_paused_fixed"
                },
                {
                    "id": "solution_paused_fixed",
                    "type": "solution",
                    "title": "Resolved: OneDrive Sync Paused",
                    "description": "OneDrive sync resumed.\n\n**Ticket Notes:** OneDrive sync was paused due to [manual pause / battery saver / metered network / bandwidth limit]. Resumed sync and adjusted settings as needed.\n\n**If battery saver is the cause:** Consider adjusting the OneDrive power settings so sync isn't paused every time the user unplugs."
                },
                {
                    "id": "check_stuck_sync",
                    "type": "action",
                    "title": "Fix Stuck OneDrive Sync",
                    "description": "Sync arrows are spinning but files aren't actually syncing.\n\n**Step 1: Check what's stuck**\nOneDrive icon > View sync problems — shows which files are stuck.\n\n**Step 2: Quick fixes (try in order):**\n1. Pause sync for 2 minutes, then resume\n2. Close and reopen OneDrive\n3. Reset OneDrive:\n```\n%localappdata%\\Microsoft\\OneDrive\\onedrive.exe /reset\n```\nIf OneDrive doesn't restart after 2 minutes:\n```\n%localappdata%\\Microsoft\\OneDrive\\onedrive.exe\n```\n\n**Step 3: If reset doesn't work:**\n- Check for a large file that's holding up the queue\n- Move the stuck file out of the OneDrive folder, let sync complete, then move it back\n- Check Event Viewer > Application log for OneDrive errors",
                    "next_node_id": "stuck_resolved"
                },
                {
                    "id": "stuck_resolved",
                    "type": "decision",
                    "question": "Did the sync resume?",
                    "help_text": "Check if files are actually uploading/downloading now",
                    "options": [
                        {"id": "yes", "label": "Yes, sync is working now", "next_node_id": "solution_stuck_fixed"},
                        {"id": "no", "label": "No, still stuck after reset", "next_node_id": "fix_onedrive_reinstall"}
                    ],
                    "children": [
                        {
                            "id": "solution_stuck_fixed",
                            "type": "solution",
                            "title": "Resolved: OneDrive Sync Unstuck",
                            "description": "OneDrive sync resumed after troubleshooting.\n\n**Ticket Notes:** OneDrive sync was stuck/not completing. Resolved by [pause-resume / OneDrive reset / removing stuck file]. All files now syncing normally.\n\n**If /reset was used:** User's sync partnerships were preserved. No data was lost."
                        },
                        {
                            "id": "fix_onedrive_reinstall",
                            "type": "action",
                            "title": "Reinstall OneDrive",
                            "description": "Reset didn't work — reinstall the OneDrive client.\n\n**Step 1: Uninstall**\nSettings > Apps > Microsoft OneDrive > Uninstall\n\n**Step 2: Delete leftover config**\n```\nrd /s /q \"%localappdata%\\Microsoft\\OneDrive\"\n```\n\n**Step 3: Reinstall**\nDownload from: https://www.microsoft.com/en-us/microsoft-365/onedrive/download\n\n**Step 4: Sign in and configure**\n- Sign in with the user's M365 account\n- Choose folders to sync\n- Enable Files On-Demand\n\n**Important:** No files are lost — everything is still in the cloud. The reinstall just rebuilds the local sync engine.",
                            "next_node_id": "solution_reinstall"
                        },
                        {
                            "id": "solution_reinstall",
                            "type": "solution",
                            "title": "Resolved: OneDrive Reinstalled",
                            "description": "OneDrive sync restored after reinstallation.\n\n**Ticket Notes:** OneDrive sync stuck and unresponsive to reset. Reinstalled OneDrive client. Sync restored. No data loss.\n\n**Post-install:** Verify Known Folder Move (KFM) is re-enabled if the org uses it (Desktop, Documents, Pictures backup)."
                        }
                    ]
                },
                {
                    "id": "check_conflicts",
                    "type": "action",
                    "title": "Resolve OneDrive Conflict Files",
                    "description": "Conflict files appear when two people (or two devices) edit the same file simultaneously and OneDrive can't merge the changes.\n\n**How to identify conflicts:**\n- Files named like: \"Report-JohnSmith.docx\" or \"Report (1).docx\"\n- OneDrive icon > View sync problems may list conflicts\n\n**Resolution steps:**\n1. Open both versions (original and conflict copy)\n2. Determine which version is correct (or merge changes manually)\n3. Keep the correct version, delete the conflict copy\n4. If it's an Office file: the original may have version history — check File > Info > Version History\n\n**Prevention:**\n- Use co-authoring (Office Online or desktop Office) instead of local editing\n- If editing offline frequently, conflicts are inevitable\n- For shared files: encourage users to communicate when editing",
                    "next_node_id": "solution_conflicts"
                },
                {
                    "id": "solution_conflicts",
                    "type": "solution",
                    "title": "Resolved: OneDrive File Conflicts",
                    "description": "Conflict files resolved.\n\n**Ticket Notes:** OneDrive sync created conflict copies due to simultaneous edits. Reviewed both versions with user and kept the correct one. Educated user on co-authoring.\n\n**Prevention:** Enable AutoSave in Office apps and use real-time co-authoring to prevent future conflicts. Avoid editing synced files offline unless necessary."
                },
                {
                    "id": "check_onedrive_running",
                    "type": "action",
                    "title": "Get OneDrive Running",
                    "description": "No OneDrive icon in the system tray means it's not running.\n\n**Step 1: Check if it's installed**\nSearch Start menu for \"OneDrive\" — if not found, it needs to be installed.\n\n**Step 2: Start OneDrive**\n```\n%localappdata%\\Microsoft\\OneDrive\\onedrive.exe\n```\n\n**Step 3: Check if it's set to start automatically**\nTask Manager > Startup tab > Look for OneDrive — should be Enabled.\n\n**Step 4: Check if it's hidden**\nClick the ^ arrow in the system tray — OneDrive may be in the overflow area.\n\n**If OneDrive isn't installed:**\nIt should be included with Windows 10/11 and Microsoft 365. If missing:\n- Download from https://www.microsoft.com/en-us/microsoft-365/onedrive/download\n- Or install Microsoft 365 Apps which includes it",
                    "next_node_id": "onedrive_started"
                },
                {
                    "id": "onedrive_started",
                    "type": "decision",
                    "question": "Is OneDrive running now?",
                    "help_text": "Check the system tray after starting it",
                    "options": [
                        {"id": "yes", "label": "Yes, it's running and syncing", "next_node_id": "solution_onedrive_started"},
                        {"id": "error", "label": "It starts but shows an error", "next_node_id": "check_sync_error"},
                        {"id": "not_installed", "label": "It's not installed", "next_node_id": "fix_onedrive_reinstall"}
                    ],
                    "children": [
                        {
                            "id": "solution_onedrive_started",
                            "type": "solution",
                            "title": "Resolved: OneDrive Started",
                            "description": "OneDrive was not running and has been started.\n\n**Ticket Notes:** OneDrive sync client was not running. Started manually and enabled auto-start. Sync is now active.\n\n**Verify:** Task Manager > Startup > OneDrive should be Enabled to prevent this from happening again."
                        }
                    ]
                }
            ]
        }
    }


# =============================================================================
# Tree 3: Mail Flow Issues (Exchange Online)
# =============================================================================
def get_mail_flow_tree() -> dict[str, Any]:
    """Mail Flow Issues - M365 tree."""
    return {
        "name": "Mail Flow Issues (Exchange Online)",
        "description": "Diagnose and resolve email delivery problems in Exchange Online / Microsoft 365. Covers NDR analysis, mail flow rules, spam filtering, connector issues, DNS records (SPF/DKIM/DMARC), and message tracing.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What is the email issue?",
            "help_text": "Identify the specific mail flow problem to narrow down the cause.",
            "options": [
                {"id": "not_receiving", "label": "User is NOT receiving emails", "next_node_id": "check_inbound"},
                {"id": "not_sending", "label": "Emails are NOT being sent (bounce/NDR)", "next_node_id": "check_ndr"},
                {"id": "delayed", "label": "Emails are delayed (slow delivery)", "next_node_id": "check_delayed"},
                {"id": "going_to_spam", "label": "Legitimate emails going to junk/spam", "next_node_id": "check_spam_filter"},
                {"id": "external_reject", "label": "External recipients say our emails go to their spam", "next_node_id": "check_outbound_reputation"}
            ],
            "children": [
                {
                    "id": "check_inbound",
                    "type": "action",
                    "title": "Run a Message Trace for Inbound Mail",
                    "description": "User isn't receiving emails. First, verify whether mail is reaching Exchange Online.\n\n**Message Trace:**\n1. M365 Admin Center > Exchange Admin Center > Mail Flow > Message Trace\n2. Search for messages TO the user within the last 48 hours\n3. Look at the status: Delivered, Failed, FilteredAsSpam, Quarantined\n\n**PowerShell alternative:**\n```\nConnect-ExchangeOnline\nGet-MessageTrace -RecipientAddress user@domain.com -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) | Format-Table Received,SenderAddress,Subject,Status\n```\n\n**Quick checks while trace runs:**\n- Is the user's mailbox full? (Check quota in Exchange Admin > Mailboxes)\n- Is there an Inbox rule moving emails? (OWA > Settings > Mail > Rules)\n- Is Focused Inbox hiding emails? (Check 'Other' tab in Outlook)",
                    "next_node_id": "inbound_trace_result"
                },
                {
                    "id": "inbound_trace_result",
                    "type": "decision",
                    "question": "What does the message trace show?",
                    "help_text": "Look at the Status column in the trace results",
                    "options": [
                        {"id": "delivered", "label": "Status: Delivered (but user doesn't see it)", "next_node_id": "check_inbox_rules"},
                        {"id": "quarantined", "label": "Status: Quarantined or FilteredAsSpam", "next_node_id": "fix_quarantine"},
                        {"id": "failed", "label": "Status: Failed", "next_node_id": "check_ndr"},
                        {"id": "no_results", "label": "No results in trace (mail never reached M365)", "next_node_id": "check_mx_records"}
                    ],
                    "children": [
                        {
                            "id": "check_inbox_rules",
                            "type": "action",
                            "title": "Check Inbox Rules and Focused Inbox",
                            "description": "Mail is delivered but user can't see it. Something is hiding or moving it.\n\n**Check Inbox Rules:**\n1. OWA (Outlook on the web) > Settings gear > View all Outlook settings > Mail > Rules\n2. Look for rules that delete, move, or redirect emails\n3. Also check: Sweep rules and Block list\n\n**PowerShell check:**\n```\nGet-InboxRule -Mailbox user@domain.com | Select Name,Description,Enabled\n```\n\n**Check Focused Inbox:**\n- Is the missing email in the 'Other' tab?\n- Right-click > Move to Focused to train it\n\n**Check Deleted Items:**\n- User may have accidentally deleted it\n- Check Deleted Items folder AND Recoverable Items\n\n**Check shared mailbox confusion:**\n- Is the user looking in the right mailbox?",
                            "next_node_id": "solution_inbox_rules"
                        },
                        {
                            "id": "solution_inbox_rules",
                            "type": "solution",
                            "title": "Resolved: Email Hidden by Inbox Rule",
                            "description": "Email was delivered but hidden by an inbox rule or Focused Inbox.\n\n**Ticket Notes:** User reported not receiving emails. Message trace confirmed delivery. Found [inbox rule moving to folder / Focused Inbox filtering to Other / accidental deletion]. Resolved by [disabling rule / moving to Focused / recovering from Deleted Items].\n\n**Tip:** Review inbox rules periodically — users often create them and forget."
                        },
                        {
                            "id": "fix_quarantine",
                            "type": "action",
                            "title": "Release from Quarantine / Fix Spam Filtering",
                            "description": "Legitimate email was quarantined by Exchange Online Protection.\n\n**Release the email:**\n1. M365 Defender > Email & Collaboration > Review > Quarantine\n2. Find the message, select it > Release\n3. Choose to release to all recipients and report as not junk\n\n**Prevent future false positives:**\n1. Add the sender to the user's Safe Senders list\n2. Or add to org-wide allow list:\n   - Exchange Admin > Mail Flow > Rules > create a rule to bypass spam filtering for trusted senders/domains\n3. Or use a Transport Rule to set SCL to -1 for the sender\n\n**PowerShell:**\n```\nGet-QuarantineMessage -RecipientAddress user@domain.com | Select Subject,SenderAddress,QuarantineTypes,ReceivedTime\n```",
                            "next_node_id": "solution_quarantine_released"
                        },
                        {
                            "id": "solution_quarantine_released",
                            "type": "solution",
                            "title": "Resolved: Email Released from Quarantine",
                            "description": "Legitimate email was quarantined by spam filter. Released and allow-listed.\n\n**Ticket Notes:** Email from [sender] quarantined by Exchange Online Protection. Released from quarantine. Added sender to [safe senders / transport rule allow list] to prevent recurrence.\n\n**Caution:** Be selective with allow lists. Only add trusted senders to avoid creating a spam bypass."
                        },
                        {
                            "id": "check_mx_records",
                            "type": "action",
                            "title": "Verify MX Records",
                            "description": "No mail is reaching Exchange Online. Check DNS.\n\n**Verify MX record:**\n```\nnslookup -type=mx domain.com\n```\n\n**Should point to:** `domain-com.mail.protection.outlook.com` (priority 0)\n\n**If MX is wrong:**\n- Was the domain recently migrated?\n- Did someone change DNS records?\n- Check the domain registrar's DNS settings\n\n**If MX is correct but mail still not arriving:**\n- Check if the domain is verified in M365 Admin Center > Domains\n- Check for a third-party mail gateway (Mimecast, Proofpoint, etc.) that might be intercepting\n- Is there a mail flow connector in Exchange Admin that's misconfigured?",
                            "next_node_id": "mx_result"
                        },
                        {
                            "id": "mx_result",
                            "type": "decision",
                            "question": "What did the MX record check show?",
                            "help_text": "Compare the MX record to what M365 expects",
                            "options": [
                                {"id": "mx_wrong", "label": "MX record is wrong or missing", "next_node_id": "solution_fix_mx"},
                                {"id": "mx_correct", "label": "MX is correct — issue is elsewhere", "next_node_id": "solution_escalate_mail"}
                            ],
                            "children": [
                                {
                                    "id": "solution_fix_mx",
                                    "type": "solution",
                                    "title": "Resolved: MX Record Corrected",
                                    "description": "MX record was missing or incorrect, preventing inbound mail.\n\n**Ticket Notes:** Inbound mail not reaching M365. MX record was [missing / pointing to old server]. Updated MX to point to [tenant].mail.protection.outlook.com with priority 0.\n\n**DNS propagation:** Changes take up to 48 hours but usually within 1-4 hours. Monitor message trace for new incoming mail.\n\n**Also verify these DNS records:**\n- SPF: `v=spf1 include:spf.protection.outlook.com -all`\n- DKIM: Enabled in M365 Defender\n- DMARC: `v=DMARC1; p=quarantine; rua=mailto:dmarc@domain.com`"
                                },
                                {
                                    "id": "solution_escalate_mail",
                                    "type": "solution",
                                    "title": "Escalate: Mail Flow Issue — Advanced Investigation",
                                    "description": "MX records are correct but mail still isn't arriving. Needs deeper investigation.\n\n**Ticket Notes:** Inbound mail not reaching user. MX records verified correct. Message trace shows no inbound messages. Escalating for connector and mail gateway investigation.\n\n**Next steps:**\n- Check Exchange Admin > Mail Flow > Connectors for misconfiguration\n- If third-party gateway (Mimecast, Proofpoint): check their admin console\n- Check M365 Defender for blocked sender patterns\n- Open Microsoft support ticket if needed"
                                }
                            ]
                        }
                    ]
                },
                {
                    "id": "check_ndr",
                    "type": "action",
                    "title": "Analyze the Bounce / NDR Message",
                    "description": "User got a Non-Delivery Report (NDR) bounce back. The error code tells you why.\n\n**Ask user to forward the NDR to you.** Key info to find:\n- The error code (e.g., 550 5.1.1, 5.7.1, 5.2.2)\n- The remote server's rejection message\n\n**Common NDR codes:**\n- **5.1.1** — Recipient doesn't exist (typo? old email?)\n- **5.2.2** — Recipient mailbox full\n- **5.7.1** — Sender not authorized (permissions or relay denied)\n- **5.7.23** — Sender domain doesn't have valid SPF/DKIM\n- **5.4.1** — Recipient domain doesn't exist (DNS issue)\n- **5.7.606/607** — Blocked by sender reputation\n- **4.4.1** — Connection timeout (remote server unreachable)\n\n**Look up the full code:** https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/non-delivery-reports-in-exchange-online/non-delivery-reports-in-exchange-online",
                    "next_node_id": "ndr_type"
                },
                {
                    "id": "ndr_type",
                    "type": "decision",
                    "question": "What NDR code did you find?",
                    "help_text": "Match the code to the most relevant category",
                    "options": [
                        {"id": "recipient_issue", "label": "5.1.1 / 5.4.1 — Recipient doesn't exist", "next_node_id": "solution_recipient_invalid"},
                        {"id": "permission", "label": "5.7.x — Permission or authentication denied", "next_node_id": "fix_send_permissions"},
                        {"id": "mailbox_full", "label": "5.2.2 — Recipient mailbox full", "next_node_id": "solution_recipient_full"},
                        {"id": "reputation", "label": "5.7.606/607 — Blocked by reputation", "next_node_id": "check_outbound_reputation"}
                    ],
                    "children": [
                        {
                            "id": "solution_recipient_invalid",
                            "type": "solution",
                            "title": "Resolved: Invalid Recipient Address",
                            "description": "Email bounced because the recipient address doesn't exist.\n\n**Ticket Notes:** NDR 5.1.1 — recipient address not found. Verified with user: [typo in address / recipient left the company / domain changed].\n\n**Actions:**\n- Double-check spelling of the recipient address\n- If the address used to work: the recipient's mailbox may have been deleted or the domain changed\n- Contact the recipient through another channel to get their current email"
                        },
                        {
                            "id": "fix_send_permissions",
                            "type": "action",
                            "title": "Fix Send Permission / Relay Issue",
                            "description": "5.7.x errors mean the sender isn't authorized.\n\n**Common scenarios:**\n\n**Sending as a shared mailbox:**\n- User needs 'Send As' or 'Send on Behalf' permission\n- Exchange Admin > Mailboxes > [shared mailbox] > Delegation\n\n**Sending to a distribution group:**\n- The group may restrict who can send to it\n- Exchange Admin > Groups > [group] > Message approval / Delivery management\n\n**External relay denied:**\n- If using a connector or application to send via M365\n- Check the connector settings in Exchange Admin > Mail Flow > Connectors\n\n**SPF/DKIM rejection (5.7.23):**\n- Your domain's SPF record doesn't include M365\n- Fix: Add `include:spf.protection.outlook.com` to your SPF record",
                            "next_node_id": "solution_send_perms"
                        },
                        {
                            "id": "solution_send_perms",
                            "type": "solution",
                            "title": "Resolved: Send Permission / Relay Fixed",
                            "description": "Mail flow restored after fixing send permissions.\n\n**Ticket Notes:** NDR 5.7.x — send permission denied. Root cause: [missing Send As permission / distribution group restriction / SPF record missing M365 / connector misconfiguration]. Resolved by [granting permission / updating restriction / fixing DNS / updating connector]."
                        },
                        {
                            "id": "solution_recipient_full",
                            "type": "solution",
                            "title": "Resolved: Recipient Mailbox Full",
                            "description": "Email bounced because the recipient's mailbox is full.\n\n**Ticket Notes:** NDR 5.2.2 — recipient mailbox is over quota. This is on the recipient's end, not ours.\n\n**If the recipient is in our org:** Increase their mailbox quota or help them clean up. Exchange Admin > Mailboxes > [user] > Mailbox usage.\n\n**If external recipient:** Inform the sender that the recipient's mailbox is full. They'll need to contact the recipient through another channel."
                        }
                    ]
                },
                {
                    "id": "check_delayed",
                    "type": "action",
                    "title": "Investigate Delayed Email Delivery",
                    "description": "Emails are arriving but with significant delays.\n\n**Run message trace to see timing:**\n```\nGet-MessageTrace -SenderAddress user@domain.com -StartDate (Get-Date).AddDays(-1) -EndDate (Get-Date) | Select Received,SenderAddress,RecipientAddress,Subject,Status\n```\n\n**Look at the 'Received' timestamps** — compare send time to delivery time.\n\n**Common delay causes:**\n1. **Mail flow rules** — A transport rule is holding mail (check Exchange Admin > Mail Flow > Rules for rules with 'defer')\n2. **Greylist** — First-time sender to a domain gets delayed (normal, resolves in 1-15 min)\n3. **Large attachments** — Big files take longer\n4. **Connectors** — If routing through a third-party gateway, check their queue\n5. **M365 service issue** — Check Service Health dashboard\n\n**If internal to internal:** Should be near-instant. Delays suggest a processing rule or health issue.",
                    "next_node_id": "solution_delayed"
                },
                {
                    "id": "solution_delayed",
                    "type": "solution",
                    "title": "Resolved: Email Delivery Delay",
                    "description": "Identified and addressed the cause of email delays.\n\n**Ticket Notes:** Email delivery delayed by [timeframe]. Root cause: [transport rule / greylisting / large attachment / connector queue / M365 service issue]. Resolution: [disabled rule / waited for greylist to clear / reduced attachment / checked connector / monitored service health].\n\n**If greylisting:** This is normal behavior for first-time senders and resolves automatically."
                },
                {
                    "id": "check_spam_filter",
                    "type": "action",
                    "title": "Fix Legitimate Email Going to Junk",
                    "description": "Good emails are being incorrectly flagged as spam.\n\n**Immediate fix for the user:**\n1. Right-click the email in Junk > Move to Inbox\n2. Right-click sender > Add to Safe Senders\n\n**Admin-level fixes:**\n1. M365 Defender > Policies > Anti-spam > check policy settings\n2. Create an allow list entry:\n   - M365 Defender > Tenant Allow/Block Lists > add sender or domain\n3. Create a transport rule:\n   - Exchange Admin > Mail Flow > Rules > Set SCL to -1 for the sender\n\n**Check why it was flagged:**\n- Message trace > click the message > see the spam confidence level (SCL)\n- Check message headers: look for `X-MS-Exchange-Organization-SCL` and `X-Forefront-Antispam-Report`",
                    "next_node_id": "solution_spam_fixed"
                },
                {
                    "id": "solution_spam_fixed",
                    "type": "solution",
                    "title": "Resolved: False Positive Spam Filtering",
                    "description": "Legitimate email no longer going to junk.\n\n**Ticket Notes:** Legitimate emails from [sender/domain] incorrectly flagged as spam. SCL was [value]. Added to [Safe Senders / Tenant Allow list / transport rule]. Emails now delivering to inbox.\n\n**Caution:** Only allow-list trusted senders. Over-permissive rules can let actual spam through."
                },
                {
                    "id": "check_outbound_reputation",
                    "type": "action",
                    "title": "Check Outbound Email Reputation and DNS",
                    "description": "External recipients say our emails go to their spam. This is a sender reputation issue.\n\n**Step 1: Check SPF, DKIM, DMARC records:**\n```\nnslookup -type=txt domain.com    # Look for SPF\nnslookup -type=txt selector1._domainkey.domain.com    # DKIM\nnslookup -type=txt _dmarc.domain.com    # DMARC\n```\n\n**Expected values:**\n- SPF: `v=spf1 include:spf.protection.outlook.com -all`\n- DKIM: Should have CNAME records for selector1 and selector2\n- DMARC: `v=DMARC1; p=quarantine;` (at minimum)\n\n**Step 2: Check blocklists**\nUse https://mxtoolbox.com/blacklists.aspx — enter your domain.\n\n**Step 3: Check in M365 Defender:**\n- Email & Collaboration > Restricted entities — is the user or domain restricted?\n- A compromised account may have been used to send spam, getting the domain blacklisted.",
                    "next_node_id": "reputation_result"
                },
                {
                    "id": "reputation_result",
                    "type": "decision",
                    "question": "What did the reputation check reveal?",
                    "help_text": "Check DNS records, blocklists, and M365 Defender",
                    "options": [
                        {"id": "dns_issue", "label": "SPF/DKIM/DMARC records missing or wrong", "next_node_id": "solution_fix_email_dns"},
                        {"id": "blocklisted", "label": "Domain or IP is on a blocklist", "next_node_id": "solution_blocklist"},
                        {"id": "restricted", "label": "User/domain restricted in M365 Defender", "next_node_id": "solution_restricted_sender"},
                        {"id": "all_ok", "label": "Everything checks out fine", "next_node_id": "solution_reputation_ok"}
                    ],
                    "children": [
                        {
                            "id": "solution_fix_email_dns",
                            "type": "solution",
                            "title": "Resolved: Email DNS Records Fixed",
                            "description": "SPF/DKIM/DMARC records were missing or misconfigured.\n\n**Ticket Notes:** Outbound emails going to recipients' spam due to missing/incorrect email authentication DNS records.\n\n**Records added/fixed:**\n- SPF: `v=spf1 include:spf.protection.outlook.com -all`\n- DKIM: Enabled via M365 Defender > Email authentication > DKIM\n- DMARC: `v=DMARC1; p=quarantine; rua=mailto:dmarc@domain.com`\n\n**Allow 24-48 hours** for DNS propagation and reputation improvement.\n\n**Verify with:** https://mxtoolbox.com/emailhealth/"
                        },
                        {
                            "id": "solution_blocklist",
                            "type": "solution",
                            "title": "Action Required: Domain/IP Blocklisted",
                            "description": "Domain or sending IP is on a third-party blocklist.\n\n**Ticket Notes:** Outbound email reputation issue. Domain/IP found on [blocklist name]. Likely caused by [spam from compromised account / misconfigured relay / new domain with no reputation].\n\n**Remediation:**\n1. Fix the root cause (secure compromised accounts, fix relay)\n2. Request delisting from each blocklist (links on mxtoolbox.com results)\n3. Most blocklists auto-delist in 24-48 hours after spam stops\n4. Check M365 Defender for compromised user accounts\n\n**For Microsoft's own blocklist:** Use the delist portal at https://sender.office.com"
                        },
                        {
                            "id": "solution_restricted_sender",
                            "type": "solution",
                            "title": "Resolved: Restricted Sender Unblocked",
                            "description": "User was restricted from sending in M365 Defender.\n\n**This usually means the account was compromised and used to send spam.**\n\n**Immediate actions:**\n1. Reset the user's password\n2. Enable MFA if not already on\n3. Revoke active sessions: `Revoke-AzureADUserAllRefreshToken -ObjectId <userID>`\n4. Remove the restriction in M365 Defender > Restricted entities\n5. Check for inbox forwarding rules the attacker may have created\n\n**Ticket Notes:** User restricted from sending due to suspected compromise. Password reset, MFA enabled, sessions revoked, forwarding rules checked. Restriction removed."
                        },
                        {
                            "id": "solution_reputation_ok",
                            "type": "solution",
                            "title": "Reputation Checks OK — Monitor Situation",
                            "description": "All email authentication records and reputation checks look clean.\n\n**Ticket Notes:** SPF/DKIM/DMARC all correctly configured. Not on any blocklists. User not restricted.\n\n**Possible causes:**\n- Recipient's mail system has aggressive filtering\n- Email content is triggering spam filters (links, attachments, formatting)\n- New domain that hasn't built reputation yet\n\n**Recommendations:**\n- Ask the recipient to add us to their safe senders\n- Avoid spammy language in emails\n- Warm up new domains by sending gradually"
                        }
                    ]
                }
            ]
        }
    }


# =============================================================================
# Tree 4: SharePoint Permissions Problems
# =============================================================================
def get_sharepoint_permissions_tree() -> dict[str, Any]:
    """SharePoint Permissions Problems - M365 tree."""
    return {
        "name": "SharePoint Permissions Problems",
        "description": "Troubleshoot SharePoint Online access denied errors, broken inheritance, external sharing failures, and permission management. Covers site-level, library-level, and item-level permissions in M365.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What is the SharePoint permissions issue?",
            "help_text": "Identify what the user is trying to do and what error they're seeing.",
            "options": [
                {"id": "access_denied", "label": "User gets 'Access Denied' or 'You need permission'", "next_node_id": "check_access_denied"},
                {"id": "external_sharing", "label": "Can't share with external users", "next_node_id": "check_external_sharing"},
                {"id": "too_much_access", "label": "Someone has access they shouldn't have", "next_node_id": "check_oversharing"},
                {"id": "request_pending", "label": "Access request sent but not approved", "next_node_id": "check_access_requests"}
            ],
            "children": [
                {
                    "id": "check_access_denied",
                    "type": "action",
                    "title": "Diagnose Access Denied Error",
                    "description": "User gets 'Access Denied' when visiting a SharePoint site, library, or file.\n\n**Step 1: Verify the URL is correct**\n- Is the user going to the right site? Typos in SharePoint URLs are common.\n\n**Step 2: Check the user's permissions**\n1. Go to the site as an admin\n2. Settings gear > Site permissions\n3. Check if the user is in any permission group (Members, Visitors, Owners)\n\n**Step 3: Check at the right level**\nSharePoint permissions can be set at:\n- **Site level** — applies to everything\n- **Library/list level** — can have unique permissions\n- **Folder level** — can override library permissions\n- **Item/file level** — most granular\n\nIf the user has site access but not file access, the file/folder probably has unique permissions (broken inheritance).\n\n**Step 4: Check Entra ID groups**\n- Permissions are often granted via security groups or M365 groups\n- The user may have been removed from a group",
                    "next_node_id": "access_denied_cause"
                },
                {
                    "id": "access_denied_cause",
                    "type": "decision",
                    "question": "Why does the user lack access?",
                    "help_text": "Based on the permission check above",
                    "options": [
                        {"id": "not_added", "label": "User was never added to the site", "next_node_id": "fix_add_user"},
                        {"id": "unique_perms", "label": "File/folder has unique permissions (broken inheritance)", "next_node_id": "fix_unique_perms"},
                        {"id": "group_removed", "label": "User was removed from a security group", "next_node_id": "fix_group_membership"},
                        {"id": "conditional_access", "label": "Conditional Access is blocking access", "next_node_id": "fix_sp_conditional_access"}
                    ],
                    "children": [
                        {
                            "id": "fix_add_user",
                            "type": "action",
                            "title": "Add User to SharePoint Site",
                            "description": "User needs to be granted access to the site.\n\n**Option 1: Add to a SharePoint group**\n1. Site > Settings gear > Site permissions\n2. Choose the appropriate group:\n   - **Visitors** = Read only\n   - **Members** = Edit (add/edit/delete content)\n   - **Owners** = Full control (manage permissions, settings)\n3. Click the group > Add members > enter user's email\n\n**Option 2: Add via M365 group (recommended)**\nIf this is a team site connected to a M365 group:\n- Adding the user to the M365 group gives them Member access automatically\n- M365 Admin > Groups > [group] > Members > Add\n\n**Option 3: Share directly**\n- Click 'Share' on the site > enter user's email > choose permission level\n\n**Best practice:** Use groups over individual permissions for easier management.",
                            "next_node_id": "solution_user_added"
                        },
                        {
                            "id": "solution_user_added",
                            "type": "solution",
                            "title": "Resolved: User Added to SharePoint Site",
                            "description": "User granted access to the SharePoint site.\n\n**Ticket Notes:** User could not access [site name]. Added to [group name] with [permission level]. User confirmed access.\n\n**Note:** Permission changes are usually immediate, but can take up to 15-30 minutes in some cases. If user still can't access, have them clear browser cache or try InPrivate/Incognito."
                        },
                        {
                            "id": "fix_unique_perms",
                            "type": "action",
                            "title": "Fix Unique Permissions (Broken Inheritance)",
                            "description": "The file or folder has its own permissions separate from the parent.\n\n**How to check:**\n1. Go to the file/folder in SharePoint\n2. Click the (i) info pane or ··· > Manage access\n3. See who has access\n\n**Options to fix:**\n\n**Add the user to this specific item:**\n- ··· > Manage access > Grant access > enter user's email\n\n**Restore inheritance (use the parent's permissions instead):**\n1. Go to Library settings > Permissions for this library\n2. Or for a folder: ··· > Manage access > look for 'Stop sharing' or 'Restore inheritance'\n\n**PowerShell (PnP) to check:**\n```\nConnect-PnPOnline -Url https://tenant.sharepoint.com/sites/sitename\nGet-PnPListItem -List 'Documents' -Id <itemId> | Get-PnPProperty -Property RoleAssignments\n```\n\n**Caution:** Restoring inheritance removes all unique permissions. Make sure that's intended.",
                            "next_node_id": "solution_unique_perms"
                        },
                        {
                            "id": "solution_unique_perms",
                            "type": "solution",
                            "title": "Resolved: Unique Permissions Fixed",
                            "description": "Access issue resolved by fixing item-level permissions.\n\n**Ticket Notes:** User couldn't access [file/folder] due to unique permissions (broken inheritance). Resolved by [granting direct access / restoring inheritance].\n\n**Recommendation:** Minimize unique permissions — they create management complexity. Use folder structure and library-level permissions where possible."
                        },
                        {
                            "id": "fix_group_membership",
                            "type": "action",
                            "title": "Restore Group Membership",
                            "description": "User lost access because they were removed from a security group or M365 group.\n\n**Identify the group:**\n1. Check site permissions to see which groups have access\n2. Check the user's group memberships in Entra ID\n\n**Re-add the user:**\n- Entra ID > Users > [user] > Groups > Add membership\n- Or M365 Admin > Groups > [group] > Add member\n\n**If it was an automated removal:**\n- Check dynamic group rules in Entra ID\n- The user may not match the membership criteria anymore (department change, license change, etc.)",
                            "next_node_id": "solution_group_restored"
                        },
                        {
                            "id": "solution_group_restored",
                            "type": "solution",
                            "title": "Resolved: Group Membership Restored",
                            "description": "Access restored by re-adding user to the group.\n\n**Ticket Notes:** User lost SharePoint access due to removal from [group name]. Re-added to group. If dynamic group: [explain why they were removed and whether the criteria should be updated].\n\n**Prevention:** Review group membership changes that could impact SharePoint access. Document which groups grant access to which sites."
                        },
                        {
                            "id": "fix_sp_conditional_access",
                            "type": "action",
                            "title": "Check Conditional Access Blocking SharePoint",
                            "description": "Conditional Access policies can block SharePoint access based on conditions.\n\n**Check sign-in logs:**\n1. Entra ID > Users > [user] > Sign-in logs\n2. Filter for SharePoint Online\n3. Look for 'Failure' with Conditional Access as the reason\n\n**Common blocks:**\n- Unmanaged device (not Intune-enrolled)\n- Untrusted network/location\n- Browser-only access when policy requires managed app\n- MFA not completed\n\n**Fixes depend on the policy:**\n- Enroll the device in Intune\n- Connect to the corporate network or VPN\n- Complete MFA enrollment\n- Request a policy exception from the security team",
                            "next_node_id": "solution_ca_fixed"
                        },
                        {
                            "id": "solution_ca_fixed",
                            "type": "solution",
                            "title": "Resolved: Conditional Access Issue",
                            "description": "SharePoint access restored after addressing Conditional Access block.\n\n**Ticket Notes:** SharePoint access denied due to Conditional Access policy [policy name]. Cause: [unmanaged device / untrusted location / missing MFA]. Resolved by [enrolling device / connecting to VPN / completing MFA].\n\n**If the policy is too restrictive:** Escalate to the security team for review."
                        }
                    ]
                },
                {
                    "id": "check_external_sharing",
                    "type": "action",
                    "title": "Diagnose External Sharing Issue",
                    "description": "User can't share a SharePoint file or site with someone outside the organization.\n\n**External sharing has multiple levels — check all of them:**\n\n**1. Tenant level:**\nSharePoint Admin Center > Policies > Sharing\n- Most permissive → Least permissive: Anyone > New/existing guests > Existing guests > Only org\n\n**2. Site level:**\nSharePoint Admin Center > Sites > Active sites > [site] > Sharing\n- Site sharing can't be more permissive than tenant level\n\n**3. Entra ID guest settings:**\nEntra ID > External Identities > External collaboration settings\n- Check if guest invitations are restricted\n\n**4. Sensitivity labels:**\n- If the site or file has a sensitivity label, it may block external sharing\n\n**5. Conditional Access:**\n- Guest access policies may be blocking",
                    "next_node_id": "external_sharing_fix"
                },
                {
                    "id": "external_sharing_fix",
                    "type": "decision",
                    "question": "Where is external sharing being blocked?",
                    "help_text": "Check tenant, site, Entra ID, and sensitivity label settings",
                    "options": [
                        {"id": "tenant_level", "label": "Tenant sharing policy is too restrictive", "next_node_id": "solution_external_sharing_tenant"},
                        {"id": "site_level", "label": "This specific site has sharing disabled", "next_node_id": "solution_external_sharing_site"},
                        {"id": "guest_policy", "label": "Entra ID guest settings are blocking", "next_node_id": "solution_external_sharing_guest"},
                        {"id": "sensitivity", "label": "Sensitivity label is preventing sharing", "next_node_id": "solution_external_sharing_label"}
                    ],
                    "children": [
                        {
                            "id": "solution_external_sharing_tenant",
                            "type": "solution",
                            "title": "Escalate: Tenant Sharing Policy",
                            "description": "The tenant-level sharing policy prevents external sharing.\n\n**Ticket Notes:** External sharing blocked by tenant-level SharePoint policy (currently set to [level]). This is an org-wide setting and requires admin approval to change.\n\n**Escalate to:** M365 Global Admin or SharePoint Admin to review the sharing policy.\n\n**Options for admin:**\n- SharePoint Admin Center > Policies > Sharing\n- Can enable for specific sites only via site-level settings\n- Consider 'New and existing guests' as a balanced option"
                        },
                        {
                            "id": "solution_external_sharing_site",
                            "type": "solution",
                            "title": "Resolved: Site Sharing Enabled",
                            "description": "External sharing enabled for the specific site.\n\n**Ticket Notes:** External sharing was disabled for [site name]. Enabled sharing at the site level per [requester/approval]. Set to [sharing level].\n\n**SharePoint Admin Center > Sites > Active sites > [site] > Sharing**\n\n**Note:** Site sharing level cannot exceed the tenant level."
                        },
                        {
                            "id": "solution_external_sharing_guest",
                            "type": "solution",
                            "title": "Escalate: Entra ID Guest Policy",
                            "description": "Guest invitations are restricted by Entra ID policies.\n\n**Ticket Notes:** External sharing blocked by Entra ID external collaboration settings. Guest invite settings are restrictive.\n\n**Escalate to:** Entra ID admin to review:\n- Entra ID > External Identities > External collaboration settings\n- Who can invite guests (members, admins only, etc.)\n- Guest user access restrictions\n- Collaboration restrictions (allow/block specific domains)"
                        },
                        {
                            "id": "solution_external_sharing_label",
                            "type": "solution",
                            "title": "Resolved: Sensitivity Label Sharing Restriction",
                            "description": "Sensitivity label on the file/site prevents external sharing.\n\n**Ticket Notes:** External sharing blocked by sensitivity label [label name] applied to [file/site]. This label's policy restricts sharing to internal users only.\n\n**Options:**\n- Remove or change the sensitivity label if appropriate (requires permission)\n- Copy the content to a new file without the label\n- Request an exception from the compliance/security team\n\n**Caution:** Sensitivity labels exist for a reason — verify the content is appropriate for external sharing before removing the label."
                        }
                    ]
                },
                {
                    "id": "check_oversharing",
                    "type": "action",
                    "title": "Audit and Remove Excess Permissions",
                    "description": "Someone has access they shouldn't have.\n\n**Check who has access:**\n1. Site: Settings gear > Site permissions > review all groups and direct users\n2. Library: Library settings > Permissions for this document library\n3. File: Click file > (i) pane > Manage access\n\n**Common oversharing causes:**\n- 'Anyone' sharing links still active\n- User shared with wrong people\n- 'Company-wide' link was used instead of specific people\n- Guest user still has access after project ended\n\n**To remove access:**\n- Site level: Remove from the SharePoint group\n- File/folder: Manage access > Remove the user or revoke the sharing link\n- Org-wide links: Manage access > delete the company-wide link\n\n**Audit trail:**\n- M365 Compliance Center > Audit log > search for sharing activities\n- Filter by user and date to see who shared what",
                    "next_node_id": "solution_oversharing_fixed"
                },
                {
                    "id": "solution_oversharing_fixed",
                    "type": "solution",
                    "title": "Resolved: Excess Permissions Removed",
                    "description": "Removed unauthorized access to SharePoint content.\n\n**Ticket Notes:** [User/group] had unintended access to [resource]. Access was granted via [sharing link / direct permission / group membership]. Removed access and [revoked sharing links / removed from group].\n\n**Recommendations:**\n- Regular permissions audits (quarterly)\n- Set sharing link expiration dates\n- Use 'Specific people' links instead of 'Anyone' or 'Company' links\n- Enable sharing notifications for site owners"
                },
                {
                    "id": "check_access_requests",
                    "type": "action",
                    "title": "Check Access Request Queue",
                    "description": "User sent an access request but it hasn't been approved.\n\n**Where access requests go:**\n1. Site owners get an email notification\n2. SharePoint site > Settings gear > Site permissions > Access Requests\n   (or Settings > Site information > View all site settings > Access requests and invitations)\n\n**If no one is receiving requests:**\n- Check who is set as the access request recipient:\n  Site settings > Access requests and invitations\n- The email might be going to a former employee or unmonitored mailbox\n\n**To approve pending requests:**\n1. Go to the access requests page\n2. Approve or decline each request\n3. Choose the permission level when approving\n\n**If access requests are disabled:**\n- Site settings > Access requests and invitations > Enable",
                    "next_node_id": "solution_access_request"
                },
                {
                    "id": "solution_access_request",
                    "type": "solution",
                    "title": "Resolved: Access Request Processed",
                    "description": "Pending access request has been approved.\n\n**Ticket Notes:** User's access request for [site/resource] was pending. Approved with [permission level]. Updated access request notification email to [current admin/owner].\n\n**Prevention:** Ensure access request notifications go to an active, monitored mailbox. Consider using a shared mailbox for site ownership."
                }
            ]
        }
    }


# =============================================================================
# Tree 5: MFA / Conditional Access Lockout
# =============================================================================
def get_mfa_lockout_tree() -> dict[str, Any]:
    """MFA / Conditional Access Lockout - M365 tree."""
    return {
        "name": "MFA / Conditional Access Lockout",
        "description": "Troubleshoot users locked out of Microsoft 365 due to MFA issues, Conditional Access policy blocks, device compliance failures, and authentication problems. Covers Authenticator app, FIDO2 keys, phone sign-in, and Entra ID diagnostics.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What MFA/authentication issue is the user experiencing?",
            "help_text": "Identify the specific lockout scenario to determine the right fix.",
            "options": [
                {"id": "lost_phone", "label": "Lost phone / can't access Authenticator app", "next_node_id": "handle_lost_mfa"},
                {"id": "mfa_denied", "label": "MFA prompt is being denied or timing out", "next_node_id": "check_mfa_denied"},
                {"id": "ca_blocked", "label": "'You can't access this' / Conditional Access block", "next_node_id": "diagnose_ca_block"},
                {"id": "new_device", "label": "Can't sign in from a new device", "next_node_id": "check_new_device"},
                {"id": "mfa_loop", "label": "Stuck in an MFA loop (keeps asking repeatedly)", "next_node_id": "fix_mfa_loop"}
            ],
            "children": [
                {
                    "id": "handle_lost_mfa",
                    "type": "action",
                    "title": "User Lost MFA Device — Restore Access",
                    "description": "User can't complete MFA because they lost their phone, got a new phone, or deleted Authenticator.\n\n**Step 1: Verify identity** — Confirm the person is who they say they are (call their known number, verify with manager, etc.)\n\n**Step 2: Check for backup methods**\nEntra ID > Users > [user] > Authentication methods\n- Do they have a backup phone number?\n- Do they have a FIDO2 key?\n- Are there other registered methods?\n\n**Step 3: If no backup methods — Admin reset MFA:**\n1. Entra ID > Users > [user] > Authentication methods\n2. Delete all registered methods (or just the lost device)\n3. The user will be prompted to register again at next sign-in\n\n**Step 4: Issue a Temporary Access Pass (recommended):**\nEntra ID > Users > [user] > Authentication methods > Add > Temporary Access Pass\n- Set expiration (e.g., 1 hour)\n- User signs in with this TAP and re-registers MFA\n\n**Important:** Never skip identity verification. MFA reset is a high-value social engineering target.",
                    "next_node_id": "mfa_reset_result"
                },
                {
                    "id": "mfa_reset_result",
                    "type": "decision",
                    "question": "Can the user sign in and re-register MFA?",
                    "help_text": "After resetting methods or issuing a TAP",
                    "options": [
                        {"id": "yes", "label": "Yes, user re-registered MFA successfully", "next_node_id": "solution_mfa_reset"},
                        {"id": "still_blocked", "label": "Still can't sign in — another policy is blocking", "next_node_id": "diagnose_ca_block"}
                    ],
                    "children": [
                        {
                            "id": "solution_mfa_reset",
                            "type": "solution",
                            "title": "Resolved: MFA Re-registered",
                            "description": "User's MFA methods have been reset and re-registered.\n\n**Ticket Notes:** User lost access to MFA device. Identity verified via [method]. Issued Temporary Access Pass. User re-registered Microsoft Authenticator on [new device].\n\n**Strongly recommend:**\n- Register at least 2 MFA methods (Authenticator + phone number)\n- Enable passwordless sign-in for better security and UX\n- Document registered methods for future reference\n\n**Security note:** Always verify identity before resetting MFA. Log this action for audit purposes."
                        }
                    ]
                },
                {
                    "id": "check_mfa_denied",
                    "type": "action",
                    "title": "Troubleshoot MFA Prompt Failures",
                    "description": "MFA prompt is sent but user can't complete it.\n\n**Common causes:**\n\n**Authenticator push not arriving:**\n- Check phone has internet connection\n- Open Authenticator app manually (sometimes push notifications are delayed)\n- Check that notifications are enabled for Authenticator in phone settings\n- Time sync: Authenticator > three dots > Settings > Time correction for codes\n\n**TOTP code not working (6-digit code):**\n- Check the time on user's phone (must be accurate)\n- Make sure they're using the code for the right account (users with multiple accounts)\n- Codes expire every 30 seconds — don't use a code that's about to expire\n\n**Phone call not working:**\n- Is the phone number correct in Entra ID?\n- Check if the user's phone can receive calls (not in airplane mode, has signal)\n- Some VoIP numbers can't receive MFA calls\n\n**SMS not arriving:**\n- Check for full inbox on the phone\n- Try sending a regular text to verify SMS works",
                    "next_node_id": "mfa_denied_result"
                },
                {
                    "id": "mfa_denied_result",
                    "type": "decision",
                    "question": "What's preventing the MFA prompt from completing?",
                    "help_text": "Based on your investigation above",
                    "options": [
                        {"id": "push_not_arriving", "label": "Push notifications not arriving", "next_node_id": "fix_push_notifications"},
                        {"id": "code_wrong", "label": "TOTP code not accepted", "next_node_id": "fix_totp_code"},
                        {"id": "phone_issue", "label": "Phone call or SMS not working", "next_node_id": "fix_phone_mfa"},
                        {"id": "fraud_alert", "label": "User sees 'fraud alert' or didn't initiate the prompt", "next_node_id": "check_fraud_alert"}
                    ],
                    "children": [
                        {
                            "id": "fix_push_notifications",
                            "type": "action",
                            "title": "Fix Authenticator Push Notifications",
                            "description": "Push notifications aren't arriving on the user's phone.\n\n**Try in order:**\n1. Open the Authenticator app manually and approve from there\n2. Check phone notification settings for Microsoft Authenticator\n3. Force close and reopen Authenticator\n4. Check that the phone has a data/Wi-Fi connection\n5. Re-register the account in Authenticator:\n   - Remove the account from Authenticator app\n   - Go to https://mysignins.microsoft.com > Security info\n   - Delete the old Authenticator registration\n   - Add it fresh\n\n**Android-specific:** Check battery optimization settings — Authenticator may be getting killed in the background. Add it to the battery optimization exclusion list.\n\n**iPhone-specific:** Settings > Notifications > Authenticator > ensure everything is enabled.",
                            "next_node_id": "solution_push_fixed"
                        },
                        {
                            "id": "solution_push_fixed",
                            "type": "solution",
                            "title": "Resolved: Push Notifications Fixed",
                            "description": "Authenticator push notifications restored.\n\n**Ticket Notes:** MFA push notifications not arriving. Resolved by [fixing notification settings / re-registering account / fixing battery optimization]. User confirmed MFA working.\n\n**Prevention:** Advise user to test MFA periodically and register a backup method."
                        },
                        {
                            "id": "fix_totp_code",
                            "type": "action",
                            "title": "Fix TOTP Code Not Accepted",
                            "description": "The 6-digit time-based code from Authenticator isn't working.\n\n**Most common cause: Clock drift**\nAuthenticator > ··· menu > Settings > Time correction for codes > Sync now\n\n**Other checks:**\n- Make sure the user is reading the code for the correct account\n- Don't use a code in the last 5 seconds (wait for the next one)\n- If using a third-party authenticator (Google Authenticator, Authy), try Microsoft Authenticator instead\n\n**If nothing works:**\n- Remove and re-add the account in Authenticator\n- Admin: Delete the method in Entra ID and have user re-register",
                            "next_node_id": "solution_totp_fixed"
                        },
                        {
                            "id": "solution_totp_fixed",
                            "type": "solution",
                            "title": "Resolved: TOTP Code Issue Fixed",
                            "description": "TOTP code now working correctly.\n\n**Ticket Notes:** MFA time-based code not accepted. Resolved by [syncing time correction / re-registering account]. User confirmed MFA working."
                        },
                        {
                            "id": "fix_phone_mfa",
                            "type": "action",
                            "title": "Fix Phone Call / SMS MFA",
                            "description": "Phone-based MFA method isn't working.\n\n**Check in Entra ID:**\n1. Users > [user] > Authentication methods\n2. Verify the phone number is correct and current\n3. Update if wrong\n\n**Phone call issues:**\n- VoIP numbers often can't receive MFA calls\n- International numbers may be blocked by M365 tenant settings\n- User may be blocking unknown callers\n\n**SMS issues:**\n- Short code SMS may be blocked by the carrier\n- Phone storage full (can't receive new texts)\n- Some prepaid plans block short codes\n\n**Best fix:** Switch to Microsoft Authenticator push or passwordless. Phone-based MFA is less reliable and less secure.",
                            "next_node_id": "solution_phone_mfa_fixed"
                        },
                        {
                            "id": "solution_phone_mfa_fixed",
                            "type": "solution",
                            "title": "Resolved: Phone MFA Fixed",
                            "description": "Phone-based MFA restored.\n\n**Ticket Notes:** MFA phone call/SMS not working. Resolved by [updating phone number / switching from VoIP / unblocking short codes].\n\n**Recommendation:** Migrate user to Microsoft Authenticator app. It's more reliable than phone/SMS and more secure."
                        },
                        {
                            "id": "check_fraud_alert",
                            "type": "action",
                            "title": "Investigate MFA Fraud Alert",
                            "description": "User received an MFA prompt they didn't initiate — this could be an attack.\n\n**This is a security incident.** Someone has the user's password and is trying to sign in.\n\n**Immediate actions:**\n1. Tell the user to DENY the MFA prompt (do NOT approve it)\n2. Reset the user's password immediately\n3. Revoke all active sessions:\n```\n# Entra ID > Users > [user] > Revoke sessions\n# Or PowerShell:\nRevoke-AzureADUserAllRefreshToken -ObjectId <userObjectId>\n```\n4. Check sign-in logs for the unauthorized attempt:\n   Entra ID > Users > [user] > Sign-in logs\n   - Where did the attempt come from? (IP, location, device)\n\n**If the user already approved a fraudulent prompt:** Treat as a compromised account. Check for inbox forwarding rules, new MFA methods added by the attacker, and data exfiltration.",
                            "next_node_id": "solution_fraud_alert"
                        },
                        {
                            "id": "solution_fraud_alert",
                            "type": "solution",
                            "title": "Security Incident: Unauthorized MFA Prompt",
                            "description": "User received MFA prompts they didn't initiate — password is compromised.\n\n**Ticket Notes:** SECURITY INCIDENT — User received unauthorized MFA prompt. Password has been compromised. Immediate actions taken:\n- Password reset\n- All sessions revoked\n- Sign-in logs reviewed (attempt from [IP/location])\n- Inbox forwarding rules checked\n- MFA methods audited for unauthorized additions\n\n**Follow-up required:**\n- Check for data access during compromised period\n- Review other accounts with the same password\n- Enable number matching in Authenticator (prevents MFA fatigue attacks)\n- Consider Conditional Access policies to block risky sign-ins"
                        }
                    ]
                },
                {
                    "id": "diagnose_ca_block",
                    "type": "action",
                    "title": "Diagnose Conditional Access Block",
                    "description": "User sees 'You cannot access this right now' or 'Access has been blocked.'\n\n**Step 1: Check sign-in logs for the exact reason**\n1. Entra ID > Users > [user] > Sign-in logs\n2. Find the failed sign-in\n3. Click it > Conditional Access tab\n4. It will show which policy blocked and why\n\n**Step 2: Use the 'What If' tool**\nEntra ID > Conditional Access > What If\n- Select the user, app, and conditions\n- It shows which policies would apply and their result\n\n**Common block reasons:**\n- Device not compliant (Intune)\n- Not on a trusted network\n- Risky sign-in detected (Identity Protection)\n- Unsupported platform (e.g., Linux when only Windows/Mac allowed)\n- Browser not supported\n- Legacy authentication attempt",
                    "next_node_id": "ca_block_cause"
                },
                {
                    "id": "ca_block_cause",
                    "type": "decision",
                    "question": "What is the Conditional Access policy blocking on?",
                    "help_text": "Check the sign-in log's Conditional Access tab",
                    "options": [
                        {"id": "device_compliance", "label": "Device not compliant (Intune)", "next_node_id": "fix_device_compliance"},
                        {"id": "location", "label": "Untrusted location / network", "next_node_id": "fix_location_ca"},
                        {"id": "risky_signin", "label": "Risky sign-in detected", "next_node_id": "fix_risky_signin"},
                        {"id": "legacy_auth", "label": "Legacy authentication blocked", "next_node_id": "fix_legacy_auth"}
                    ],
                    "children": [
                        {
                            "id": "fix_device_compliance",
                            "type": "action",
                            "title": "Fix Device Compliance for Conditional Access",
                            "description": "The device doesn't meet Intune compliance requirements.\n\n**Check compliance status:**\n1. On the device: Settings > Accounts > Access work or school > Info > Sync\n2. Intune Admin > Devices > [device] > Device compliance\n\n**Common compliance failures:**\n- Windows updates not current\n- BitLocker not enabled\n- Antivirus not running or out of date\n- OS version too old\n- Device not enrolled in Intune at all\n\n**Fixes:**\n- Install pending Windows updates\n- Enable BitLocker: Control Panel > BitLocker Drive Encryption\n- Verify antivirus is running and updated\n- Re-sync Intune: Settings > Accounts > Access work or school > Info > Sync\n\n**If device isn't enrolled:** Settings > Accounts > Access work or school > Connect > sign in with work account",
                            "next_node_id": "solution_compliance_fixed"
                        },
                        {
                            "id": "solution_compliance_fixed",
                            "type": "solution",
                            "title": "Resolved: Device Compliance Restored",
                            "description": "Device is now compliant and Conditional Access allows sign-in.\n\n**Ticket Notes:** Sign-in blocked by Conditional Access due to device non-compliance. Device was [not enrolled / missing updates / BitLocker disabled / AV outdated]. Fixed compliance issue and synced with Intune. User confirmed access.\n\n**Note:** Compliance check can take up to 8 hours to update. Force a sync to speed it up."
                        },
                        {
                            "id": "fix_location_ca",
                            "type": "action",
                            "title": "Address Location-Based Block",
                            "description": "User is signing in from a location not on the trusted network list.\n\n**Common scenarios:**\n- Working from home (home IP not trusted)\n- Traveling (hotel/airport Wi-Fi)\n- Using mobile data\n- VPN disconnected\n\n**Fixes:**\n1. Connect to the corporate VPN (if available)\n2. If working from home is normal: ask admin to add the policy exception\n3. Check if the policy allows compliant devices from any location\n\n**Admin fix (if appropriate):**\nEntra ID > Conditional Access > Named locations\n- Add trusted IP ranges or countries\n- Modify the policy to allow compliant devices from any location",
                            "next_node_id": "solution_location_fixed"
                        },
                        {
                            "id": "solution_location_fixed",
                            "type": "solution",
                            "title": "Resolved: Location-Based Access",
                            "description": "Access restored after addressing location-based Conditional Access block.\n\n**Ticket Notes:** Sign-in blocked by location-based Conditional Access policy. User was at [location]. Resolved by [connecting to VPN / adding location to trusted list / policy exception].\n\n**If policy change was needed:** Document the change and get approval from the security team."
                        },
                        {
                            "id": "fix_risky_signin",
                            "type": "action",
                            "title": "Handle Risky Sign-In Block",
                            "description": "Entra ID Identity Protection flagged this sign-in as risky.\n\n**Check the risk details:**\nEntra ID > Security > Risky sign-ins > find the event\n- Risk level: Low, Medium, High\n- Risk type: Unfamiliar location, impossible travel, malware-linked IP, etc.\n\n**If it's a legitimate sign-in (false positive):**\n1. Entra ID > Security > Risky sign-ins\n2. Select the sign-in > Confirm safe\n3. This teaches the system and clears the block\n\n**If it might be real:**\n- Reset the user's password\n- Check for compromise indicators\n- Review sign-in history for other suspicious activity\n\n**Admin can also:**\nEntra ID > Security > Risky users > [user] > Dismiss risk (if confirmed safe)",
                            "next_node_id": "solution_risky_signin"
                        },
                        {
                            "id": "solution_risky_signin",
                            "type": "solution",
                            "title": "Resolved: Risky Sign-In Addressed",
                            "description": "Risky sign-in alert resolved.\n\n**Ticket Notes:** Sign-in blocked by Identity Protection risk policy. Risk type: [type]. Risk level: [level]. Determined to be [legitimate / suspicious]. Action taken: [confirmed safe / password reset / investigation].\n\n**If confirmed safe:** Educate user that signing in from new locations or devices may trigger risk alerts. This is expected and protective."
                        },
                        {
                            "id": "fix_legacy_auth",
                            "type": "action",
                            "title": "Fix Legacy Authentication Block",
                            "description": "User is trying to authenticate using a legacy protocol that's been blocked.\n\n**Legacy auth protocols (should be blocked):**\n- POP3, IMAP, SMTP AUTH\n- Older Office clients (Office 2010 and earlier)\n- ActiveSync with basic auth\n\n**How to identify:**\nSign-in logs > Client app column will show 'Other clients' or specific legacy protocol names.\n\n**Fixes:**\n- Update Office to a modern version (2016+ with modern auth, ideally M365 Apps)\n- Update email client to support OAuth/modern auth\n- If using IMAP/POP for a specific need: consider an app password (if allowed by policy)\n\n**If it's a printer or scanner using SMTP:**\n- Configure it to use SMTP AUTH with OAuth or use a relay connector\n- Or use a service account with an exception (security team approval needed)",
                            "next_node_id": "solution_legacy_auth"
                        },
                        {
                            "id": "solution_legacy_auth",
                            "type": "solution",
                            "title": "Resolved: Legacy Auth Updated",
                            "description": "Legacy authentication issue resolved by updating to modern auth.\n\n**Ticket Notes:** Sign-in blocked due to legacy authentication protocol [protocol]. Resolved by [updating Office / switching to modern auth client / configuring SMTP relay].\n\n**Important:** Legacy auth should remain blocked. It's a major security vulnerability (no MFA support). Any exceptions should be temporary and documented."
                        }
                    ]
                },
                {
                    "id": "check_new_device",
                    "type": "action",
                    "title": "Troubleshoot New Device Sign-In",
                    "description": "User can't sign into M365 from a new computer or phone.\n\n**Common blockers for new devices:**\n\n1. **MFA re-verification required** — Normal. User needs their MFA device.\n2. **Device enrollment required** — Conditional Access may require Intune enrollment.\n3. **Browser not supported** — Some policies block non-Edge/Chrome browsers.\n4. **Risk detection** — New device triggers Identity Protection alert.\n\n**Check sign-in logs** to see exactly what's blocking:\nEntra ID > Users > [user] > Sign-in logs > most recent failed attempt\n\n**If Intune enrollment is required:**\n1. Go to Settings > Accounts > Access work or school > Connect\n2. Sign in with work credentials\n3. Follow the enrollment prompts\n4. Wait for compliance check (can take up to 30 minutes)\n5. Try signing in again",
                    "next_node_id": "ca_block_cause"
                },
                {
                    "id": "fix_mfa_loop",
                    "type": "action",
                    "title": "Fix MFA Authentication Loop",
                    "description": "User keeps getting prompted for MFA repeatedly — completes it but gets asked again.\n\n**Common causes:**\n\n**Browser cookie issues:**\n1. Clear browser cookies and cache\n2. Try InPrivate/Incognito mode\n3. Try a different browser\n\n**Multiple Conditional Access policies conflicting:**\n- Check sign-in logs — are multiple policies triggering?\n- Use the What If tool to see all policies that apply\n\n**Session token issues:**\n1. Close ALL browser tabs\n2. Clear cookies for login.microsoftonline.com and login.windows.net\n3. Sign in fresh\n\n**If using an app (not browser):**\n- Sign out of the app completely\n- Clear app data/cache\n- Sign in again\n\n**Persistent MFA prompt on every sign-in:**\n- Check if the 'remember MFA' setting is enabled for the tenant\n- Entra ID > Conditional Access > [policy] > Session > Sign-in frequency",
                    "next_node_id": "solution_mfa_loop"
                },
                {
                    "id": "solution_mfa_loop",
                    "type": "solution",
                    "title": "Resolved: MFA Loop Fixed",
                    "description": "MFA authentication loop resolved.\n\n**Ticket Notes:** User stuck in MFA re-prompt loop. Resolved by [clearing browser cookies / fixing conflicting CA policies / clearing app cache / adjusting sign-in frequency].\n\n**If sign-in frequency policy is too aggressive:** Discuss with security team about adjusting the policy. Requiring MFA every hour causes user frustration without proportional security benefit for most scenarios."
                }
            ]
        }
    }


# =============================================================================
# Tree 6: License Assignment Problems
# =============================================================================
def get_license_assignment_tree() -> dict[str, Any]:
    """License Assignment Problems - M365 tree."""
    return {
        "name": "License Assignment Problems",
        "description": "Troubleshoot Microsoft 365 license assignment failures, service plan conflicts, group-based licensing issues, and users missing expected M365 features. Covers direct and group-based assignment in Entra ID.",
        "category": "Microsoft 365",
        "tree_structure": {
            "id": "root",
            "type": "decision",
            "question": "What is the licensing issue?",
            "help_text": "Identify whether this is a missing license, failed assignment, or feature availability issue.",
            "options": [
                {"id": "no_license", "label": "User doesn't have a license / apps not available", "next_node_id": "check_license_status"},
                {"id": "assignment_failed", "label": "License assignment is failing with an error", "next_node_id": "check_assignment_error"},
                {"id": "missing_feature", "label": "User has a license but a specific feature is missing", "next_node_id": "check_service_plans"},
                {"id": "no_licenses_available", "label": "No available licenses to assign (out of seats)", "next_node_id": "check_license_count"}
            ],
            "children": [
                {
                    "id": "check_license_status",
                    "type": "action",
                    "title": "Check User's Current License Assignment",
                    "description": "Verify what licenses the user currently has.\n\n**M365 Admin Center:**\nUsers > Active users > [user] > Licenses and apps tab\n\n**Entra ID:**\nUsers > [user] > Licenses\n\n**PowerShell:**\n```\nConnect-MgGraph -Scopes 'User.Read.All'\nGet-MgUserLicenseDetail -UserId user@domain.com | Select SkuPartNumber\n```\n\n**Check if the license is direct or group-based:**\n- In Entra ID > Users > [user] > Licenses\n- The 'Assignment path' column shows 'Direct' or the group name\n\n**Common M365 license SKUs:**\n- ENTERPRISEPACK = Office 365 E3\n- SPE_E5 = Microsoft 365 E5\n- SPE_E3 = Microsoft 365 E3\n- EXCHANGESTANDARD = Exchange Online Plan 1\n- TEAMS_EXPLORATORY = Teams Exploratory",
                    "next_node_id": "license_status_result"
                },
                {
                    "id": "license_status_result",
                    "type": "decision",
                    "question": "What does the license check show?",
                    "help_text": "Review the user's assigned licenses",
                    "options": [
                        {"id": "no_license_at_all", "label": "No license assigned at all", "next_node_id": "assign_license"},
                        {"id": "wrong_license", "label": "Has a license but it's the wrong one", "next_node_id": "fix_wrong_license"},
                        {"id": "has_license", "label": "Has the right license but apps still not working", "next_node_id": "check_service_plans"}
                    ],
                    "children": [
                        {
                            "id": "assign_license",
                            "type": "action",
                            "title": "Assign a License to the User",
                            "description": "User needs a license assigned.\n\n**Option 1: Direct assignment**\n1. M365 Admin Center > Users > [user] > Licenses and apps\n2. Check the appropriate license\n3. Expand the license to enable/disable specific service plans if needed\n4. Save\n\n**Option 2: Group-based assignment (recommended for scale)**\n1. Entra ID > Groups > [licensing group]\n2. Add the user to the group\n3. The license auto-assigns based on group membership\n\n**PowerShell:**\n```\nSet-MgUserLicense -UserId user@domain.com -AddLicenses @{SkuId='<sku-id>'} -RemoveLicenses @()\n```\n\n**Important:** The user needs a Usage Location set in their profile before a license can be assigned. Check: Entra ID > Users > [user] > Properties > Usage location.",
                            "next_node_id": "solution_license_assigned"
                        },
                        {
                            "id": "solution_license_assigned",
                            "type": "solution",
                            "title": "Resolved: License Assigned",
                            "description": "License successfully assigned to the user.\n\n**Ticket Notes:** User had no M365 license. Assigned [license name] via [direct / group-based assignment]. Services should be available within [minutes to 24 hours depending on service].\n\n**Provisioning times:**\n- Exchange mailbox: Usually within 15-30 minutes\n- Teams: Usually within minutes\n- OneDrive: First access provisions the site (up to 24 hours)\n- Office apps: Available to install immediately\n\n**User action needed:** Sign out and back into Office apps to pick up the new license."
                        },
                        {
                            "id": "fix_wrong_license",
                            "type": "action",
                            "title": "Change User's License",
                            "description": "User has the wrong license type. Need to swap it.\n\n**Important:** Removing a license removes access to its services. Swap carefully.\n\n**Best practice: Add new license FIRST, then remove old one.**\n\n1. M365 Admin Center > Users > [user] > Licenses and apps\n2. Check the new license\n3. Uncheck the old license\n4. Save (both changes happen atomically)\n\n**If group-based:**\n- Add user to the new license group\n- Wait for the new license to apply\n- Remove from the old license group\n\n**Watch for:**\n- Exchange mailbox data — swapping licenses can temporarily disconnect the mailbox\n- OneDrive data — user's files are preserved but access may hiccup\n- Teams — may need to re-sign in\n\n**Data is NOT deleted immediately.** M365 keeps data for 30 days after license removal.",
                            "next_node_id": "solution_license_swapped"
                        },
                        {
                            "id": "solution_license_swapped",
                            "type": "solution",
                            "title": "Resolved: License Changed",
                            "description": "User's license has been updated.\n\n**Ticket Notes:** Changed user's license from [old license] to [new license]. New license assigned first before removing old to prevent service disruption.\n\n**Follow-up:** Have user sign out and back into all Office apps and Teams to pick up the new license. Verify all services are accessible."
                        }
                    ]
                },
                {
                    "id": "check_assignment_error",
                    "type": "action",
                    "title": "Diagnose License Assignment Error",
                    "description": "License assignment is failing. Check for common errors.\n\n**Check in Entra ID:**\nUsers > [user] > Licenses — look for error indicators\n\n**For group-based licensing errors:**\nEntra ID > Groups > [group] > Licenses > check for users in error state\n\n**Common errors:**\n\n1. **\"Usage location is not specified\"**\n   - User needs a Usage Location set\n   - Entra ID > Users > [user] > Properties > Edit > Usage location\n\n2. **\"Conflicting service plans\"**\n   - Two licenses include the same service (e.g., two different Exchange plans)\n   - Disable the conflicting service plan in one of the licenses\n\n3. **\"Not enough licenses\"**\n   - All purchased seats are assigned\n   - Buy more or reclaim unused licenses\n\n4. **\"Dependent service plan disabled\"**\n   - A required sub-service was disabled (e.g., SharePoint needed for OneDrive)\n   - Re-enable the dependent service plan",
                    "next_node_id": "assignment_error_type"
                },
                {
                    "id": "assignment_error_type",
                    "type": "decision",
                    "question": "What is the assignment error?",
                    "help_text": "Based on the error message in Entra ID",
                    "options": [
                        {"id": "no_location", "label": "Usage location not specified", "next_node_id": "fix_usage_location"},
                        {"id": "conflict", "label": "Conflicting service plans", "next_node_id": "fix_service_conflict"},
                        {"id": "no_seats", "label": "Not enough licenses available", "next_node_id": "check_license_count"},
                        {"id": "dependency", "label": "Dependent service plan disabled", "next_node_id": "fix_dependency"}
                    ],
                    "children": [
                        {
                            "id": "fix_usage_location",
                            "type": "action",
                            "title": "Set Usage Location",
                            "description": "M365 requires a Usage Location before licenses can be assigned. This determines which services are available based on regional availability.\n\n**Set the location:**\n1. Entra ID > Users > [user] > Properties > Edit\n2. Set \"Usage location\" to the appropriate country\n3. Save\n\n**PowerShell:**\n```\nUpdate-MgUser -UserId user@domain.com -UsageLocation 'US'\n```\n\n**For bulk users:** Check if your user provisioning (Entra Connect, SCIM, etc.) is setting this automatically. If not, add it to your onboarding process.\n\n**After setting location:** Retry the license assignment.",
                            "next_node_id": "solution_location_set"
                        },
                        {
                            "id": "solution_location_set",
                            "type": "solution",
                            "title": "Resolved: Usage Location Set",
                            "description": "Usage location set and license assigned successfully.\n\n**Ticket Notes:** License assignment failed due to missing Usage Location. Set to [country]. License [name] now assigned successfully.\n\n**Prevention:** Ensure Usage Location is set during user onboarding. Add it to your new user checklist or automate it in your provisioning workflow."
                        },
                        {
                            "id": "fix_service_conflict",
                            "type": "action",
                            "title": "Resolve Service Plan Conflict",
                            "description": "Two licenses are trying to assign the same service plan, causing a conflict.\n\n**Example:** User has Exchange Online Plan 2 from one license AND an E3 license that also includes Exchange. These conflict.\n\n**How to fix:**\n1. Entra ID > Users > [user] > Licenses\n2. Click on one of the conflicting licenses\n3. Disable the overlapping service plan (toggle it off)\n4. Save\n5. The other license assignment should now succeed\n\n**Common conflicts:**\n- Exchange Online Plan 1 vs Plan 2\n- Teams Exploratory vs E3/E5 Teams\n- Power BI Free vs Power BI Pro\n- Visio/Project standalone vs E5 included\n\n**For group-based licensing:** You may need to create a different group with specific service plans disabled to avoid conflicts.",
                            "next_node_id": "solution_conflict_resolved"
                        },
                        {
                            "id": "solution_conflict_resolved",
                            "type": "solution",
                            "title": "Resolved: Service Plan Conflict Fixed",
                            "description": "License conflict resolved by disabling the overlapping service plan.\n\n**Ticket Notes:** License assignment failed due to conflicting service plans ([plan A] from [license A] conflicting with [plan B] from [license B]). Disabled [plan] from [license]. Both licenses now assigned.\n\n**Prevention:** When adding new license SKUs to the tenant, review which service plans overlap with existing assignments."
                        },
                        {
                            "id": "fix_dependency",
                            "type": "action",
                            "title": "Fix Dependent Service Plan",
                            "description": "A required service plan was disabled, preventing another from working.\n\n**Common dependencies:**\n- OneDrive requires SharePoint Online\n- Teams requires SharePoint Online and Exchange Online\n- Office apps require various background services\n\n**Fix:**\n1. Entra ID > Users > [user] > Licenses\n2. Click the license\n3. Enable the required service plan that was disabled\n4. Save\n\n**The error message usually tells you which plan is needed.** Re-enable it and the dependent service will work.",
                            "next_node_id": "solution_dependency_fixed"
                        },
                        {
                            "id": "solution_dependency_fixed",
                            "type": "solution",
                            "title": "Resolved: Dependent Service Enabled",
                            "description": "Required service plan re-enabled, resolving the dependency error.\n\n**Ticket Notes:** License service plan [dependent plan] required [prerequisite plan] to be enabled. Re-enabled [prerequisite]. All services now functional.\n\n**Caution:** Be careful when disabling individual service plans within a license — dependencies aren't always obvious."
                        }
                    ]
                },
                {
                    "id": "check_service_plans",
                    "type": "action",
                    "title": "Check Individual Service Plans",
                    "description": "User has the right license but a specific feature or app is unavailable.\n\n**Check which service plans are enabled:**\n1. Entra ID > Users > [user] > Licenses\n2. Click the license > see the list of service plans with on/off toggles\n3. Is the service for the missing feature turned ON?\n\n**Examples:**\n- Teams missing? → Check 'Microsoft Teams' service plan\n- Can't install Office? → Check 'Microsoft 365 Apps for Enterprise'\n- No Power BI? → Check 'Power BI' service plan\n- OneDrive not working? → Check 'SharePoint Online' (OneDrive depends on it)\n\n**If the service plan is ON but feature still missing:**\n- Wait up to 24 hours for provisioning\n- Have user sign out and back in\n- Check for a Conditional Access policy blocking the specific app",
                    "next_node_id": "service_plan_result"
                },
                {
                    "id": "service_plan_result",
                    "type": "decision",
                    "question": "Was the service plan disabled?",
                    "help_text": "Check the service plan toggles in the license",
                    "options": [
                        {"id": "was_disabled", "label": "Yes, the service plan was toggled off — enabled it", "next_node_id": "solution_service_plan_enabled"},
                        {"id": "already_on", "label": "Service plan is on but feature still missing", "next_node_id": "solution_wait_provisioning"}
                    ],
                    "children": [
                        {
                            "id": "solution_service_plan_enabled",
                            "type": "solution",
                            "title": "Resolved: Service Plan Enabled",
                            "description": "Missing feature restored by enabling the service plan.\n\n**Ticket Notes:** User missing [feature]. Service plan [plan name] was disabled in [license name]. Enabled the service plan. Feature should be available within [minutes to 24 hours].\n\n**User action:** Sign out and back into all M365 apps to pick up the change."
                        },
                        {
                            "id": "solution_wait_provisioning",
                            "type": "solution",
                            "title": "Service Plan Enabled — Wait for Provisioning",
                            "description": "Service plan is correctly enabled but the feature isn't available yet.\n\n**Ticket Notes:** User missing [feature]. Service plan verified as enabled. Likely a provisioning delay.\n\n**Provisioning times:**\n- Exchange mailbox: 15-30 minutes\n- Teams: Minutes\n- OneDrive site: Up to 24 hours (first access)\n- Office app downloads: Immediate\n- SharePoint sites: Minutes to hours\n\n**If still not working after 24 hours:** Open a Microsoft support ticket."
                        }
                    ]
                },
                {
                    "id": "check_license_count",
                    "type": "action",
                    "title": "Check Available License Count",
                    "description": "Organization may be out of available license seats.\n\n**Check license inventory:**\n1. M365 Admin Center > Billing > Licenses\n2. See total, assigned, and available for each SKU\n\n**PowerShell:**\n```\nGet-MgSubscribedSku | Select SkuPartNumber,\n    @{N='Total';E={$_.PrepaidUnits.Enabled}},\n    @{N='Assigned';E={$_.ConsumedUnits}},\n    @{N='Available';E={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}}\n```\n\n**If no licenses available:**\n1. **Reclaim unused licenses:** Find users who are licensed but inactive\n   - M365 Admin Center > Reports > Usage\n   - Look for users with no activity in 90+ days\n2. **Buy more licenses:** Billing > Purchase services\n3. **Reassign from a departing user:** Remove their license first\n\n**For group-based licensing:** The group will show users in 'error' state if no seats are available.",
                    "next_node_id": "license_count_result"
                },
                {
                    "id": "license_count_result",
                    "type": "decision",
                    "question": "Are there licenses available to assign?",
                    "help_text": "Check the available count in M365 Admin",
                    "options": [
                        {"id": "reclaimed", "label": "Found unused licenses to reclaim", "next_node_id": "solution_license_reclaimed"},
                        {"id": "need_purchase", "label": "Need to purchase more licenses", "next_node_id": "solution_purchase_licenses"}
                    ],
                    "children": [
                        {
                            "id": "solution_license_reclaimed",
                            "type": "solution",
                            "title": "Resolved: Licenses Reclaimed",
                            "description": "Freed up licenses by reclaiming from inactive users.\n\n**Ticket Notes:** No available [license name] seats. Identified [X] inactive users and reclaimed their licenses. Assigned one to [requesting user].\n\n**Recommendation:** Set up a quarterly license review process to reclaim unused seats. M365 usage reports help identify inactive users."
                        },
                        {
                            "id": "solution_purchase_licenses",
                            "type": "solution",
                            "title": "Action Required: Purchase Additional Licenses",
                            "description": "No available licenses and none to reclaim. Need to purchase more.\n\n**Ticket Notes:** All [license name] seats are assigned and in active use. Need to purchase additional licenses.\n\n**Next steps:**\n- M365 Admin Center > Billing > Purchase services\n- Or contact your Microsoft partner/reseller\n- Licenses are billed monthly or annually depending on your agreement\n\n**Escalate to:** Whoever approves software purchases (IT manager, finance, etc.)"
                        }
                    ]
                }
            ]
        }
    }