---
phase: 07-user-access-audit
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - SharepointToolbox/Core/Models/UserAccessEntry.cs
  - SharepointToolbox/Services/IUserAccessAuditService.cs
  - SharepointToolbox/Services/IGraphUserSearchService.cs
autonomous: true
requirements:
  - UACC-01
  - UACC-02
must_haves:
  truths:
    - "UserAccessEntry record exists with all fields needed for audit results display and export"
    - "IUserAccessAuditService interface defines the contract for scanning permissions filtered by user"
    - "IGraphUserSearchService interface defines the contract for Graph API people-picker autocomplete"
    - "AccessType enum distinguishes Direct, Group, and Inherited access"
  artifacts:
    - path: "SharepointToolbox/Core/Models/UserAccessEntry.cs"
      provides: "Data model for user-centric audit results"
      contains: "record UserAccessEntry"
    - path: "SharepointToolbox/Services/IUserAccessAuditService.cs"
      provides: "Service contract for user access auditing"
      contains: "interface IUserAccessAuditService"
    - path: "SharepointToolbox/Services/IGraphUserSearchService.cs"
      provides: "Service contract for Graph API user search"
      contains: "interface IGraphUserSearchService"
  key_links: []
---

<objective>
Define the data models and service interfaces that all subsequent plans depend on. This is the Wave 0 contract layer: UserAccessEntry record, AccessType enum, IUserAccessAuditService, and IGraphUserSearchService.

Purpose: Every other plan in this phase imports these types. Defining them first prevents circular dependencies and gives executors concrete contracts.
Output: UserAccessEntry.cs, IUserAccessAuditService.cs, IGraphUserSearchService.cs
</objective>

<execution_context>
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/07-user-access-audit/07-CONTEXT.md

<interfaces>
<!-- Existing models this builds alongside -->
From SharepointToolbox/Core/Models/PermissionEntry.cs:
```csharp
namespace SharepointToolbox.Core.Models;

public record PermissionEntry(
    string ObjectType,          // "Site Collection" | "Site" | "List" | "Folder"
    string Title,
    string Url,
    bool   HasUniquePermissions,
    string Users,               // Semicolon-joined display names
    string UserLogins,          // Semicolon-joined login names
    string PermissionLevels,    // Semicolon-joined role names
    string GrantedThrough,      // "Direct Permissions" | "SharePoint Group: <name>"
    string PrincipalType        // "SharePointGroup" | "User" | "External User"
);
```

From SharepointToolbox/Core/Models/SiteInfo.cs:
```csharp
namespace SharepointToolbox.Core.Models;
public record SiteInfo(string Url, string Title);
```

From SharepointToolbox/Core/Models/ScanOptions.cs (inferred from usage):
```csharp
public record ScanOptions(bool IncludeInherited, bool ScanFolders, int FolderDepth, bool IncludeSubsites);
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create UserAccessEntry model and AccessType enum</name>
  <files>SharepointToolbox/Core/Models/UserAccessEntry.cs</files>
  <action>
    Create `SharepointToolbox/Core/Models/UserAccessEntry.cs` with:

    ```csharp
    namespace SharepointToolbox.Core.Models;

    /// <summary>
    /// Classifies how a user received a permission assignment.
    /// </summary>
    public enum AccessType
    {
        /// <summary>User is directly assigned a role on the object.</summary>
        Direct,
        /// <summary>User is a member of a SharePoint group that has the role.</summary>
        Group,
        /// <summary>Permission is inherited from a parent object (not unique).</summary>
        Inherited
    }

    /// <summary>
    /// One row in the User Access Audit results grid.
    /// Represents a single permission that a specific user holds on a specific object.
    /// </summary>
    public record UserAccessEntry(
        string UserDisplayName,     // e.g. "Alice Smith"
        string UserLogin,           // e.g. "alice@contoso.com" or "i:0#.f|membership|alice@contoso.com"
        string SiteUrl,             // The site collection URL where this permission exists
        string SiteTitle,           // The site collection title
        string ObjectType,          // "Site Collection" | "Site" | "List" | "Folder"
        string ObjectTitle,         // Name of the list/folder/site
        string ObjectUrl,           // URL of the specific object
        string PermissionLevel,     // e.g. "Full Control", "Contribute"
        AccessType AccessType,      // Direct | Group | Inherited
        string GrantedThrough,      // "Direct Permissions" | "SharePoint Group: Members" etc.
        bool IsHighPrivilege,       // True for Full Control, Site Collection Administrator
        bool IsExternalUser         // True if login contains #EXT#
    );
    ```

    Design notes:
    - Each row is one user + one object + one permission level (fully denormalized for DataGrid binding)
    - IsHighPrivilege pre-computed during scan for warning icon display without re-evaluation
    - IsExternalUser pre-computed using PermissionEntryHelper.IsExternalUser pattern
    - SiteUrl + SiteTitle included so results can group by site across multi-site scans
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>UserAccessEntry.cs and AccessType enum exist in Core/Models/, compile without errors, contain all 12 fields.</done>
</task>

<task type="auto">
  <name>Task 2: Create IUserAccessAuditService and IGraphUserSearchService interfaces</name>
  <files>SharepointToolbox/Services/IUserAccessAuditService.cs, SharepointToolbox/Services/IGraphUserSearchService.cs</files>
  <action>
    Create `SharepointToolbox/Services/IUserAccessAuditService.cs`:

    ```csharp
    using SharepointToolbox.Core.Models;

    namespace SharepointToolbox.Services;

    /// <summary>
    /// Scans permissions across selected sites and filters results to show
    /// only what specific user(s) can access.
    /// </summary>
    public interface IUserAccessAuditService
    {
        /// <summary>
        /// Scans all selected sites for permissions, then filters results to entries
        /// matching the specified user logins. Returns a flat list of UserAccessEntry
        /// records suitable for DataGrid binding and export.
        /// </summary>
        /// <param name="sessionManager">Session manager for creating authenticated contexts.</param>
        /// <param name="targetUserLogins">Login names (emails) of users to audit.</param>
        /// <param name="sites">Sites to scan.</param>
        /// <param name="options">Scan depth options (inherited, folders, subsites).</param>
        /// <param name="progress">Progress reporter.</param>
        /// <param name="ct">Cancellation token.</param>
        /// <returns>Flat list of access entries for the target users.</returns>
        Task<IReadOnlyList<UserAccessEntry>> AuditUsersAsync(
            ISessionManager sessionManager,
            IReadOnlyList<string> targetUserLogins,
            IReadOnlyList<SiteInfo> sites,
            ScanOptions options,
            IProgress<OperationProgress> progress,
            CancellationToken ct);
    }
    ```

    Create `SharepointToolbox/Services/IGraphUserSearchService.cs`:

    ```csharp
    namespace SharepointToolbox.Services;

    /// <summary>
    /// Searches tenant users via Microsoft Graph API for the people-picker autocomplete.
    /// </summary>
    public interface IGraphUserSearchService
    {
        /// <summary>
        /// Searches for users in the tenant whose display name or email matches the query.
        /// Returns up to <paramref name="maxResults"/> matches.
        /// </summary>
        /// <param name="clientId">The Azure AD app client ID for Graph authentication.</param>
        /// <param name="query">Partial name or email to search for.</param>
        /// <param name="maxResults">Maximum number of results to return (default 10).</param>
        /// <param name="ct">Cancellation token.</param>
        /// <returns>List of (DisplayName, Email/UPN) tuples.</returns>
        Task<IReadOnlyList<GraphUserResult>> SearchUsersAsync(
            string clientId,
            string query,
            int maxResults = 10,
            CancellationToken ct = default);
    }

    /// <summary>
    /// Represents a user returned by the Graph API people search.
    /// </summary>
    public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail);
    ```
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
  </verify>
  <done>Both interface files exist in Services/, compile without errors, IUserAccessAuditService.AuditUsersAsync and IGraphUserSearchService.SearchUsersAsync are defined with correct signatures.</done>
</task>

</tasks>

<verification>
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- UserAccessEntry.cs contains record with 12 fields and AccessType enum
- IUserAccessAuditService.cs contains AuditUsersAsync method signature
- IGraphUserSearchService.cs contains SearchUsersAsync method signature and GraphUserResult record
</verification>

<success_criteria>
All three files compile cleanly. The contracts are established: downstream plans (07-02 through 07-08) can import UserAccessEntry, AccessType, IUserAccessAuditService, IGraphUserSearchService, and GraphUserResult without ambiguity.
</success_criteria>

<output>
After completion, create `.planning/phases/07-user-access-audit/07-01-SUMMARY.md`
</output>
