--- phase: 08-simplified-permissions plan: 01 type: execute wave: 1 depends_on: [] files_modified: - SharepointToolbox/Core/Models/RiskLevel.cs - SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs - SharepointToolbox/Core/Models/PermissionSummary.cs - SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs autonomous: true requirements: - SIMP-01 - SIMP-02 must_haves: truths: - "RiskLevel enum distinguishes High, Medium, Low, and ReadOnly access tiers" - "PermissionLevelMapping maps all standard SharePoint role names to plain-language labels and risk levels" - "SimplifiedPermissionEntry wraps PermissionEntry with computed simplified labels and risk level without modifying the original record" - "PermissionSummary groups permission entries by risk level with counts" - "Unknown/custom role names fall back to the raw name with a Medium risk level" artifacts: - path: "SharepointToolbox/Core/Models/RiskLevel.cs" provides: "Risk level classification enum" contains: "enum RiskLevel" - path: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs" provides: "Static mapping from SP role names to plain-language labels" contains: "class PermissionLevelMapping" - path: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs" provides: "Presentation wrapper for PermissionEntry with simplified fields" contains: "class SimplifiedPermissionEntry" - path: "SharepointToolbox/Core/Models/PermissionSummary.cs" provides: "Aggregation model for summary counts by risk level" contains: "record PermissionSummary" key_links: - from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs" to: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs" via: "Static method call to resolve labels and risk level" pattern: "PermissionLevelMapping\\.Get" - from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs" to: "SharepointToolbox/Core/Models/PermissionEntry.cs" via: "Wraps original entry as Inner property" pattern: "PermissionEntry Inner" --- Define the data models and mapping layer for simplified permissions: RiskLevel enum, PermissionLevelMapping helper, SimplifiedPermissionEntry wrapper, and PermissionSummary aggregation model. Purpose: All subsequent plans import these types. The mapping layer is the core of SIMP-01 (plain-language labels) and SIMP-02 (risk level color coding). PermissionEntry is immutable and NOT modified — SimplifiedPermissionEntry wraps it as a presentation concern. Output: RiskLevel.cs, PermissionLevelMapping.cs, SimplifiedPermissionEntry.cs, PermissionSummary.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 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 (Limited Access already removed) string GrantedThrough, // "Direct Permissions" | "SharePoint Group: " string PrincipalType // "SharePointGroup" | "User" | "External User" ); ``` From SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs: ```csharp public static class PermissionEntryHelper { public static bool IsExternalUser(string loginName); public static IReadOnlyList FilterPermissionLevels(IEnumerable levels); public static bool IsSharingLinksGroup(string loginName); } ``` Task 1: Create RiskLevel enum and PermissionLevelMapping helper SharepointToolbox/Core/Models/RiskLevel.cs, SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs Create `SharepointToolbox/Core/Models/RiskLevel.cs`: ```csharp namespace SharepointToolbox.Core.Models; /// /// Classifies a SharePoint permission level by its access risk. /// Used for color coding in both WPF DataGrid and HTML export. /// public enum RiskLevel { /// Full Control, Site Collection Administrator — can delete site, manage permissions. High, /// Contribute, Edit, Design — can modify content. Medium, /// Read, Restricted View — can view but not modify. Low, /// View Only — most restricted legitimate access. ReadOnly } ``` Create `SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs`: ```csharp using SharepointToolbox.Core.Models; namespace SharepointToolbox.Core.Helpers; /// /// Maps SharePoint built-in permission level names to human-readable labels and risk levels. /// Used by SimplifiedPermissionEntry and export services to translate raw role names /// into plain-language descriptions that non-technical users can understand. /// public static class PermissionLevelMapping { /// /// Result of looking up a SharePoint role name. /// public record MappingResult(string Label, RiskLevel RiskLevel); /// /// Known SharePoint built-in permission level mappings. /// Keys are case-insensitive via the dictionary comparer. /// private static readonly Dictionary Mappings = new(StringComparer.OrdinalIgnoreCase) { // High risk — full administrative access ["Full Control"] = new("Full control (can manage everything)", RiskLevel.High), ["Site Collection Administrator"] = new("Site collection admin (full control)", RiskLevel.High), // Medium risk — can modify content ["Contribute"] = new("Can edit files and list items", RiskLevel.Medium), ["Edit"] = new("Can edit files, lists, and pages", RiskLevel.Medium), ["Design"] = new("Can edit pages and use design tools", RiskLevel.Medium), ["Approve"] = new("Can approve content and list items", RiskLevel.Medium), ["Manage Hierarchy"] = new("Can create sites and manage pages", RiskLevel.Medium), // Low risk — read access ["Read"] = new("Can view files and pages", RiskLevel.Low), ["Restricted Read"] = new("Can view pages only (no download)", RiskLevel.Low), // Read-only — most restricted ["View Only"] = new("Can view files in browser only", RiskLevel.ReadOnly), ["Restricted View"] = new("Restricted view access", RiskLevel.ReadOnly), }; /// /// Gets the human-readable label and risk level for a SharePoint role name. /// Returns the mapped result for known roles; for unknown/custom roles, /// returns the raw name as-is with Medium risk level. /// public static MappingResult GetMapping(string roleName) { if (string.IsNullOrWhiteSpace(roleName)) return new MappingResult(roleName, RiskLevel.Low); return Mappings.TryGetValue(roleName.Trim(), out var result) ? result : new MappingResult(roleName.Trim(), RiskLevel.Medium); } /// /// Resolves a semicolon-delimited PermissionLevels string into individual mapping results. /// This handles the PermissionEntry.PermissionLevels format (e.g. "Full Control; Contribute"). /// public static IReadOnlyList GetMappings(string permissionLevels) { if (string.IsNullOrWhiteSpace(permissionLevels)) return Array.Empty(); return permissionLevels .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(GetMapping) .ToList(); } /// /// Returns the highest (most dangerous) risk level from a semicolon-delimited permission levels string. /// Used for row-level color coding when an entry has multiple roles. /// public static RiskLevel GetHighestRisk(string permissionLevels) { var mappings = GetMappings(permissionLevels); if (mappings.Count == 0) return RiskLevel.Low; // High < Medium < Low < ReadOnly in enum order — Min gives highest risk return mappings.Min(m => m.RiskLevel); } /// /// Converts a semicolon-delimited PermissionLevels string into a simplified labels string. /// E.g. "Full Control; Contribute" becomes "Full control (can manage everything); Can edit files and list items" /// public static string GetSimplifiedLabels(string permissionLevels) { var mappings = GetMappings(permissionLevels); return string.Join("; ", mappings.Select(m => m.Label)); } } ``` Design notes: - Case-insensitive lookup handles variations in SharePoint role name casing - Unknown/custom roles default to Medium (conservative — forces admin review) - GetHighestRisk uses enum ordering (High=0 is most dangerous) for row-level color - Semicolon-split methods handle the PermissionEntry.PermissionLevels format directly cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 RiskLevel.cs contains 4-value enum (High, Medium, Low, ReadOnly). PermissionLevelMapping.cs has GetMapping, GetMappings, GetHighestRisk, and GetSimplifiedLabels. All standard SP roles mapped. Unknown roles fallback to Medium. Project compiles. Task 2: Create SimplifiedPermissionEntry wrapper and PermissionSummary model SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs, SharepointToolbox/Core/Models/PermissionSummary.cs Create `SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs`: ```csharp using SharepointToolbox.Core.Helpers; namespace SharepointToolbox.Core.Models; /// /// Presentation wrapper around PermissionEntry that adds simplified labels /// and risk level classification without modifying the immutable source record. /// Used as the DataGrid ItemsSource when simplified mode is active. /// public class SimplifiedPermissionEntry { /// The original immutable PermissionEntry. public PermissionEntry Inner { get; } /// /// Human-readable labels for the permission levels. /// E.g. "Can edit files and list items" instead of "Contribute". /// public string SimplifiedLabels { get; } /// /// The highest risk level across all permission levels on this entry. /// Used for row-level color coding. /// public RiskLevel RiskLevel { get; } /// /// Individual mapping results for each permission level in the entry. /// Used when detailed breakdown per-role is needed. /// public IReadOnlyList Mappings { get; } // ── Passthrough properties for DataGrid binding ── public string ObjectType => Inner.ObjectType; public string Title => Inner.Title; public string Url => Inner.Url; public bool HasUniquePermissions => Inner.HasUniquePermissions; public string Users => Inner.Users; public string UserLogins => Inner.UserLogins; public string PermissionLevels => Inner.PermissionLevels; public string GrantedThrough => Inner.GrantedThrough; public string PrincipalType => Inner.PrincipalType; public SimplifiedPermissionEntry(PermissionEntry entry) { Inner = entry; Mappings = PermissionLevelMapping.GetMappings(entry.PermissionLevels); SimplifiedLabels = PermissionLevelMapping.GetSimplifiedLabels(entry.PermissionLevels); RiskLevel = PermissionLevelMapping.GetHighestRisk(entry.PermissionLevels); } /// /// Creates SimplifiedPermissionEntry wrappers for a collection of entries. /// public static IReadOnlyList WrapAll( IEnumerable entries) { return entries.Select(e => new SimplifiedPermissionEntry(e)).ToList(); } } ``` Create `SharepointToolbox/Core/Models/PermissionSummary.cs`: ```csharp namespace SharepointToolbox.Core.Models; /// /// Summary counts of permission entries grouped by risk level. /// Displayed in the summary panel when simplified mode is active. /// public record PermissionSummary( /// Label for this group (e.g. "High Risk", "Read Only"). string Label, /// The risk level this group represents. RiskLevel RiskLevel, /// Number of permission entries at this risk level. int Count, /// Number of distinct users at this risk level. int DistinctUsers ); /// /// Computes PermissionSummary groups from SimplifiedPermissionEntry collections. /// public static class PermissionSummaryBuilder { /// /// Risk level display labels. /// private static readonly Dictionary Labels = new() { [RiskLevel.High] = "High Risk", [RiskLevel.Medium] = "Medium Risk", [RiskLevel.Low] = "Low Risk", [RiskLevel.ReadOnly] = "Read Only", }; /// /// Builds summary counts grouped by risk level from a collection of simplified entries. /// Always returns all 4 risk levels, even if count is 0, for consistent UI binding. /// public static IReadOnlyList Build( IEnumerable entries) { var grouped = entries .GroupBy(e => e.RiskLevel) .ToDictionary(g => g.Key, g => g.ToList()); return Enum.GetValues() .Select(level => { var items = grouped.GetValueOrDefault(level, new List()); var distinctUsers = items .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)) .Select(u => u.Trim()) .Where(u => u.Length > 0) .Distinct(StringComparer.OrdinalIgnoreCase) .Count(); return new PermissionSummary( Label: Labels[level], RiskLevel: level, Count: items.Count, DistinctUsers: distinctUsers); }) .ToList(); } } ``` Design notes: - SimplifiedPermissionEntry is a class (not record) so it can have passthrough properties for DataGrid binding - All original PermissionEntry fields are exposed as passthrough properties — DataGrid columns bind identically - SimplifiedLabels and RiskLevel are computed once at construction — no per-render cost - PermissionSummaryBuilder.Build always returns 4 entries (one per RiskLevel) for consistent summary panel layout - DistinctUsers uses case-insensitive comparison for login deduplication cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 SimplifiedPermissionEntry wraps PermissionEntry with SimplifiedLabels, RiskLevel, Mappings, and all passthrough properties. PermissionSummary + PermissionSummaryBuilder provide grouped counts. Project compiles cleanly. PermissionEntry.cs is NOT modified. - `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors - RiskLevel.cs has High, Medium, Low, ReadOnly values - PermissionLevelMapping has 11 known role mappings with labels and risk levels - SimplifiedPermissionEntry wraps PermissionEntry (Inner property) without modifying it - PermissionSummaryBuilder.Build returns 4 summary entries (one per risk level) - No changes to PermissionEntry.cs All 4 files compile cleanly. The mapping and wrapper layer is complete: downstream plans (08-02 through 08-05) can import RiskLevel, PermissionLevelMapping, SimplifiedPermissionEntry, and PermissionSummary without ambiguity. PermissionEntry remains immutable and unmodified. After completion, create `.planning/phases/08-simplified-permissions/08-01-SUMMARY.md`