--- 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: [] --- 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 @C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/dev/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-user-access-audit/07-CONTEXT.md 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: " 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); ``` Task 1: Create UserAccessEntry model and AccessType enum SharepointToolbox/Core/Models/UserAccessEntry.cs Create `SharepointToolbox/Core/Models/UserAccessEntry.cs` with: ```csharp namespace SharepointToolbox.Core.Models; /// /// Classifies how a user received a permission assignment. /// public enum AccessType { /// User is directly assigned a role on the object. Direct, /// User is a member of a SharePoint group that has the role. Group, /// Permission is inherited from a parent object (not unique). Inherited } /// /// One row in the User Access Audit results grid. /// Represents a single permission that a specific user holds on a specific object. /// 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 cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 UserAccessEntry.cs and AccessType enum exist in Core/Models/, compile without errors, contain all 12 fields. Task 2: Create IUserAccessAuditService and IGraphUserSearchService interfaces SharepointToolbox/Services/IUserAccessAuditService.cs, SharepointToolbox/Services/IGraphUserSearchService.cs Create `SharepointToolbox/Services/IUserAccessAuditService.cs`: ```csharp using SharepointToolbox.Core.Models; namespace SharepointToolbox.Services; /// /// Scans permissions across selected sites and filters results to show /// only what specific user(s) can access. /// public interface IUserAccessAuditService { /// /// 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. /// /// Session manager for creating authenticated contexts. /// Login names (emails) of users to audit. /// Sites to scan. /// Scan depth options (inherited, folders, subsites). /// Progress reporter. /// Cancellation token. /// Flat list of access entries for the target users. Task> AuditUsersAsync( ISessionManager sessionManager, IReadOnlyList targetUserLogins, IReadOnlyList sites, ScanOptions options, IProgress progress, CancellationToken ct); } ``` Create `SharepointToolbox/Services/IGraphUserSearchService.cs`: ```csharp namespace SharepointToolbox.Services; /// /// Searches tenant users via Microsoft Graph API for the people-picker autocomplete. /// public interface IGraphUserSearchService { /// /// Searches for users in the tenant whose display name or email matches the query. /// Returns up to matches. /// /// The Azure AD app client ID for Graph authentication. /// Partial name or email to search for. /// Maximum number of results to return (default 10). /// Cancellation token. /// List of (DisplayName, Email/UPN) tuples. Task> SearchUsersAsync( string clientId, string query, int maxResults = 10, CancellationToken ct = default); } /// /// Represents a user returned by the Graph API people search. /// public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail); ``` cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 Both interface files exist in Services/, compile without errors, IUserAccessAuditService.AuditUsersAsync and IGraphUserSearchService.SearchUsersAsync are defined with correct signatures. - `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 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. After completion, create `.planning/phases/07-user-access-audit/07-01-SUMMARY.md`