--- phase: 15 title: Consolidation Data Model status: ready-for-planning created: 2026-04-09 --- # Phase 15 Context: Consolidation Data Model ## Decided Areas (from prior research + STATE.md) These are locked — do not re-litigate during planning or execution. | Decision | Value | |---|---| | Consolidation scope | User access audit report only — site-centric permission report is unchanged | | Source model | `UserAccessEntry` (already normalized, one user per row) | | Consolidation is opt-in | Defaults to OFF; toggle wired in Phase 16 | | No API calls | Pure data transformation — no Graph or CSOM calls | | Existing exports unchanged | When consolidation is not applied, output is identical to pre-v2.3 | ## Discussed Areas ### 1. Consolidation Key (What Defines "Same Access") **Decision:** Merge rows only when all four fields match: `UserLogin` + `PermissionLevel` + `AccessType` + `GrantedThrough`. - Strictest matching — preserves the audit trail of how access was granted - A user with "Contribute (Direct)" on 3 sites and "Contribute (Group: Members)" on 2 sites produces 2 consolidated rows, not 1 - `UserLogin` is the identity key (not `UserDisplayName`, which could vary) - `AccessType` enum values: Direct, Group, Inherited — all treated as distinct - `GrantedThrough` string comparison is exact (e.g., "SharePoint Group: Members" vs "SharePoint Group: Owners" are separate) ### 2. Merged Locations Model **Decision:** `List` with a `LocationCount` convenience property. - `ConsolidatedPermissionEntry` holds all fields from the consolidation key plus a `List` containing each merged site's URL and title - `LocationInfo` is a lightweight record: `{ string SiteUrl, string SiteTitle, string ObjectTitle, string ObjectUrl, string ObjectType }` - `LocationCount` is a computed property (`Locations.Count`) — convenience for display and sorting - No information loss — all original location data is preserved in the list - Presentation decisions (how to render the list) are deferred to Phase 16 ### 3. Report Scope **Decision:** Consolidation applies to user access audit (`UserAccessEntry`) only. - The user access audit report is already user-centric and normalized (one user per row) — natural fit for "merge same user across locations" - The site-centric permission report (`PermissionEntry`) flows the opposite direction (site → users); consolidating it would mean "same permission set across sites" — a different feature entirely - `HtmlExportService` (site-centric) is untouched by this phase - `UserAccessHtmlExportService` will receive consolidated data in Phase 16; this phase only builds the model and service ## Deferred Ideas (out of scope for Phase 15) - Consolidation toggle UI (Phase 16) - Consolidated view rendering in HTML exports (Phase 16) - Group expansion within consolidated rows (Phase 17) - Consolidation in CSV exports (out of scope per REQUIREMENTS.md) - "Same permission set across sites" consolidation for site-centric report (not planned) ## code_context | Asset | Path | Reuse | |---|---|---| | UserAccessEntry model | `SharepointToolbox/Core/Models/UserAccessEntry.cs` | Source model — consolidated entry mirrors its fields + locations list | | UserAccessAuditService | `SharepointToolbox/Services/UserAccessAuditService.cs` | Produces the `UserAccessEntry` list that feeds the consolidator | | UserAccessHtmlExportService | `SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs` | Downstream consumer in Phase 16 — must accept both flat and consolidated lists | | DuplicatesService grouping pattern | `SharepointToolbox/Services/DuplicatesService.cs` | Reference for composite-key grouping via `MakeKey()` pattern | | PermissionSummaryBuilder | `SharepointToolbox/Core/Helpers/PermissionSummaryBuilder.cs` | Reference for aggregation pattern over permission data | | Test project | `SharepointToolbox.Tests/` | New tests for PermissionConsolidator with known input/output pairs |