Files
Sharepoint-Toolbox/.planning/phases/15-consolidation-data-model/15-01-PLAN.md
Dev f5b3f08f88 docs(15): create consolidation data model phase plans
Two plans for Phase 15: models + consolidator service (wave 1), unit tests + build verification (wave 2).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:36:42 +02:00

9.1 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
15-consolidation-data-model 01 execute 1
SharepointToolbox/Core/Models/LocationInfo.cs
SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs
SharepointToolbox/Core/Helpers/PermissionConsolidator.cs
true
RPT-04
truths artifacts key_links
LocationInfo record holds five location fields from UserAccessEntry
ConsolidatedPermissionEntry holds key fields plus a list of LocationInfo with LocationCount
PermissionConsolidator.Consolidate merges entries with identical key into single rows
MakeKey uses pipe-delimited case-insensitive composite of UserLogin+PermissionLevel+AccessType+GrantedThrough
Empty input returns empty list
path provides contains
SharepointToolbox/Core/Models/LocationInfo.cs Location data record public record LocationInfo
path provides contains
SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs Consolidated permission model public record ConsolidatedPermissionEntry
path provides exports
SharepointToolbox/Core/Helpers/PermissionConsolidator.cs Consolidation logic
Consolidate
MakeKey
from to via pattern
SharepointToolbox/Core/Helpers/PermissionConsolidator.cs SharepointToolbox/Core/Models/UserAccessEntry.cs accepts IReadOnlyList<UserAccessEntry> IReadOnlyList<UserAccessEntry>
from to via pattern
SharepointToolbox/Core/Helpers/PermissionConsolidator.cs SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs returns IReadOnlyList<ConsolidatedPermissionEntry> IReadOnlyList<ConsolidatedPermissionEntry>
from to via pattern
SharepointToolbox/Core/Helpers/PermissionConsolidator.cs SharepointToolbox/Core/Models/LocationInfo.cs constructs LocationInfo from UserAccessEntry fields new LocationInfo
Create the consolidation data model and merge service for permission report consolidation.

Purpose: Establish the data shape (LocationInfo, ConsolidatedPermissionEntry) and pure-function merge logic (PermissionConsolidator) so that Phase 16 can wire them into the export pipeline. Zero API calls, zero UI — just models and a static helper.

Output: Three production files — two model records and one static consolidation service.

<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>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/15-consolidation-data-model/15-CONTEXT.md @.planning/phases/15-consolidation-data-model/15-RESEARCH.md ```csharp namespace SharepointToolbox.Core.Models;

public enum AccessType { Direct, Group, Inherited }

public record UserAccessEntry( string UserDisplayName, string UserLogin, string SiteUrl, string SiteTitle, string ObjectType, string ObjectTitle, string ObjectUrl, string PermissionLevel, AccessType AccessType, string GrantedThrough, bool IsHighPrivilege, bool IsExternalUser );

</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create LocationInfo and ConsolidatedPermissionEntry model records</name>
  <files>SharepointToolbox/Core/Models/LocationInfo.cs, SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs</files>
  <action>
Create two new C# positional record files in `Core/Models/`.

**LocationInfo.cs** — namespace `SharepointToolbox.Core.Models`:
```csharp
public record LocationInfo(
    string SiteUrl,
    string SiteTitle,
    string ObjectTitle,
    string ObjectUrl,
    string ObjectType
);

Lightweight record holding the five location-related fields extracted from UserAccessEntry when rows are merged.

ConsolidatedPermissionEntry.cs — namespace SharepointToolbox.Core.Models:

public record ConsolidatedPermissionEntry(
    string UserDisplayName,
    string UserLogin,
    string PermissionLevel,
    AccessType AccessType,
    string GrantedThrough,
    bool IsHighPrivilege,
    bool IsExternalUser,
    IReadOnlyList<LocationInfo> Locations
)
{
    public int LocationCount => Locations.Count;
}
  • Holds the four key fields (UserLogin, PermissionLevel, AccessType, GrantedThrough) plus carried-forward fields (UserDisplayName, IsHighPrivilege, IsExternalUser).
  • Locations is an IReadOnlyList<LocationInfo> containing all merged locations.
  • LocationCount is a computed convenience property.
  • Do NOT add any methods, constructors, or logic beyond the record definition and LocationCount property. cd C:/Users/dev/Documents/projets/Sharepoint && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -v q 2>&1 | tail -5 Both record files exist, compile without errors, and are in the SharepointToolbox.Core.Models namespace. ConsolidatedPermissionEntry.LocationCount returns Locations.Count.
Task 2: Create PermissionConsolidator static helper SharepointToolbox/Core/Helpers/PermissionConsolidator.cs Create `PermissionConsolidator.cs` in `Core/Helpers/` — namespace `SharepointToolbox.Core.Helpers`.

Follow the existing DuplicatesService.MakeKey() pattern for composite key generation.

using SharepointToolbox.Core.Models;

namespace SharepointToolbox.Core.Helpers;

/// <summary>
/// Merges a flat list of UserAccessEntry rows into consolidated entries
/// where rows with identical (UserLogin, PermissionLevel, AccessType, GrantedThrough)
/// are grouped into a single row with multiple locations.
/// </summary>
public static class PermissionConsolidator
{
    /// <summary>
    /// Builds a pipe-delimited, case-insensitive composite key from the four key fields.
    /// </summary>
    internal static string MakeKey(UserAccessEntry entry)
    {
        return string.Join("|",
            entry.UserLogin.ToLowerInvariant(),
            entry.PermissionLevel.ToLowerInvariant(),
            entry.AccessType.ToString(),
            entry.GrantedThrough.ToLowerInvariant());
    }

    /// <summary>
    /// Groups entries by composite key and returns consolidated rows.
    /// Each group's first entry provides UserDisplayName, IsHighPrivilege, IsExternalUser.
    /// All entries in a group contribute a LocationInfo to the Locations list.
    /// Results are ordered by UserLogin then PermissionLevel.
    /// </summary>
    public static IReadOnlyList<ConsolidatedPermissionEntry> Consolidate(
        IReadOnlyList<UserAccessEntry> entries)
    {
        if (entries.Count == 0)
            return Array.Empty<ConsolidatedPermissionEntry>();

        return entries
            .GroupBy(e => MakeKey(e))
            .Select(g =>
            {
                var first = g.First();
                var locations = g.Select(e => new LocationInfo(
                    e.SiteUrl, e.SiteTitle, e.ObjectTitle, e.ObjectUrl, e.ObjectType
                )).ToList();

                return new ConsolidatedPermissionEntry(
                    first.UserDisplayName,
                    first.UserLogin,
                    first.PermissionLevel,
                    first.AccessType,
                    first.GrantedThrough,
                    first.IsHighPrivilege,
                    first.IsExternalUser,
                    locations);
            })
            .OrderBy(c => c.UserLogin)
            .ThenBy(c => c.PermissionLevel)
            .ToList();
    }
}

Key implementation details:

  • MakeKey is internal so tests can access it via [InternalsVisibleTo] or by testing through Consolidate.
  • Use .ToLowerInvariant() on UserLogin, PermissionLevel, GrantedThrough (string key fields). AccessType is an enum — use .ToString() (case-stable).
  • Empty input short-circuits to Array.Empty<>().
  • LINQ GroupBy + Select pattern — no mutable dictionaries.
  • OrderBy UserLogin then PermissionLevel for deterministic output. cd C:/Users/dev/Documents/projets/Sharepoint && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-restore -v q 2>&1 | tail -5 PermissionConsolidator.cs compiles. Consolidate method accepts IReadOnlyList of UserAccessEntry, returns IReadOnlyList of ConsolidatedPermissionEntry. MakeKey produces pipe-delimited lowercase composite key.
Full solution build passes: ```bash cd C:/Users/dev/Documents/projets/Sharepoint && dotnet build --no-restore -v q ```

<success_criteria>

  • LocationInfo.cs and ConsolidatedPermissionEntry.cs exist in Core/Models/ with correct record signatures
  • PermissionConsolidator.cs exists in Core/Helpers/ with Consolidate and MakeKey methods
  • All three files compile as part of the SharepointToolbox project
  • No changes to any existing files </success_criteria>
After completion, create `.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md`