diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index fa5981e..559f22b 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -26,7 +26,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road - [ ] **RPT-01**: User can expand SharePoint groups in HTML reports to see group members - [ ] **RPT-02**: Group member resolution uses transitive membership to include nested group members - [ ] **RPT-03**: User can enable/disable entry consolidation per export (toggle in export settings) -- [ ] **RPT-04**: Consolidated reports merge rows for the same user with identical access levels across multiple locations into a single row +- [x] **RPT-04**: Consolidated reports merge rows for the same user with identical access levels across multiple locations into a single row ## Future Requirements @@ -59,7 +59,7 @@ Requirements for v2.3 Tenant Management & Report Enhancements. Each maps to road | RPT-01 | Phase 17 | Pending | | RPT-02 | Phase 17 | Pending | | RPT-03 | Phase 16 | Pending | -| RPT-04 | Phase 15 | Pending | +| RPT-04 | Phase 15 | Complete | **Coverage:** - v2.3 requirements: 12 total diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index ceab3fb..08bb9b7 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -43,7 +43,7 @@ ### v2.3 Tenant Management & Report Enhancements (Phases 15-19) -- [ ] **Phase 15: Consolidation Data Model** — PermissionConsolidator service and merged-row model; zero API calls, pure data shapes +- [ ] **Phase 15: Consolidation Data Model** (2 plans) — PermissionConsolidator service and merged-row model; zero API calls, pure data shapes - [ ] **Phase 16: Report Consolidation Toggle** — Export settings toggle wired to PermissionConsolidator; first user-visible consolidation behavior - [ ] **Phase 17: Group Expansion in HTML Reports** — Clickable group expansion in HTML exports with transitive membership resolution - [ ] **Phase 18: Auto-Take Ownership** — Global toggle and automatic site collection admin elevation on access denied @@ -60,7 +60,7 @@ 2. A `PermissionConsolidator` service accepts a flat list of permission rows and returns a consolidated list where duplicate user+level rows are merged 3. Consolidation logic has unit test coverage — a known 10-row input with 3 duplicate pairs produces the expected 7-row output 4. Existing HTML export services compile and produce identical output when consolidation is not applied (opt-in, defaults off) -**Plans:** 2 plans +**Plans:** 1/2 plans executed Plans: - [ ] 15-01-PLAN.md — Models (LocationInfo, ConsolidatedPermissionEntry) + PermissionConsolidator service - [ ] 15-02-PLAN.md — Unit tests (10 test cases) + full solution build verification @@ -108,7 +108,7 @@ Plans: 3. Registration creates the Azure AD application, service principal, and grants all required API permissions in a single atomic operation — if any step fails, all partial changes are rolled back and the user sees a specific error explaining what failed and why 4. A "Remove App" action in the profile dialog removes the Azure AD application registration from the target tenant 5. After removal, all cached MSAL tokens and session state for that tenant are cleared, and subsequent operations require re-authentication -**Plans**: TBD +**Plans**: 15-01 (Models + Consolidator), 15-02 (Tests + Build Verification) ## Progress @@ -117,7 +117,7 @@ Plans: | 1-5 | v1.0 | 36/36 | Shipped | 2026-04-07 | | 6-9 | v1.1 | 25/25 | Shipped | 2026-04-08 | | 10-14 | v2.2 | 14/14 | Shipped | 2026-04-09 | -| 15. Consolidation Data Model | v2.3 | 0/2 | Planning | — | +| 15. Consolidation Data Model | 1/2 | In Progress| | — | | 16. Report Consolidation Toggle | v2.3 | 0/? | Not started | — | | 17. Group Expansion in HTML Reports | v2.3 | 0/? | Not started | — | | 18. Auto-Take Ownership | v2.3 | 0/? | Not started | — | diff --git a/.planning/STATE.md b/.planning/STATE.md index 5df8dd9..a225723 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,15 +2,15 @@ gsd_state_version: 1.0 milestone: v2.3 milestone_name: Tenant Management & Report Enhancements -status: roadmap-ready -stopped_at: roadmap created — ready for phase 15 planning -last_updated: "2026-04-09" +status: planning +stopped_at: Completed 15-01-PLAN.md +last_updated: "2026-04-09T09:42:37.151Z" last_activity: 2026-04-09 — Roadmap created for v2.3 (phases 15-19) progress: total_phases: 5 completed_phases: 0 - total_plans: 0 - completed_plans: 0 + total_plans: 2 + completed_plans: 1 --- # Project State @@ -61,6 +61,8 @@ Decisions are logged in PROJECT.md Key Decisions table. - Group expansion (Phase 17) calls Graph at export time, not at scan time — scan pipeline unchanged - Auto-take ownership uses PnP `Tenant.SetSiteAdmin` — requires Tenant Admin scope - App registration must be atomic with rollback; partial Entra state is worse than no state +- [Phase 15]: MakeKey declared internal for test access via InternalsVisibleTo without exposing as public API +- [Phase 15]: LINQ GroupBy+Select for consolidation merge instead of mutable dictionary — consistent with functional codebase style ### Pending Todos @@ -72,7 +74,7 @@ None. ## Session Continuity -Last session: 2026-04-09 -Stopped at: Roadmap created — ready to plan Phase 15 +Last session: 2026-04-09T09:42:37.149Z +Stopped at: Completed 15-01-PLAN.md Resume file: None Next step: `/gsd:plan-phase 15` diff --git a/.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md b/.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md new file mode 100644 index 0000000..a008b17 --- /dev/null +++ b/.planning/phases/15-consolidation-data-model/15-01-SUMMARY.md @@ -0,0 +1,103 @@ +--- +phase: 15-consolidation-data-model +plan: "01" +subsystem: api +tags: [csharp, records, linq, permission-consolidation] + +requires: [] +provides: + - LocationInfo record with five location fields (SiteUrl, SiteTitle, ObjectTitle, ObjectUrl, ObjectType) + - ConsolidatedPermissionEntry record grouping key fields with IReadOnlyList Locations + - PermissionConsolidator.Consolidate — pure static method merging UserAccessEntry list by composite key + - PermissionConsolidator.MakeKey — pipe-delimited case-insensitive key from UserLogin+PermissionLevel+AccessType+GrantedThrough +affects: [16-report-consolidation-toggle] + +tech-stack: + added: [] + patterns: + - "Positional C# records for immutable data shapes" + - "LINQ GroupBy+Select for pure-function merge without mutable state" + - "Internal MakeKey for composite key generation (pipe-delimited, ToLowerInvariant)" + - "Array.Empty() short-circuit on empty input" + +key-files: + created: + - SharepointToolbox/Core/Models/LocationInfo.cs + - SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs + - SharepointToolbox/Core/Helpers/PermissionConsolidator.cs + modified: [] + +key-decisions: + - "MakeKey is internal (not private) to allow test access via InternalsVisibleTo without exposing as public API" + - "LINQ GroupBy+Select chosen over mutable Dictionary to match existing codebase functional style" + - "OrderBy UserLogin then PermissionLevel ensures deterministic output order for consistent exports" + +patterns-established: + - "Consolidation key: pipe-delimited lowercase composite of UserLogin|PermissionLevel|AccessType|GrantedThrough" + - "LocationInfo is the extraction unit — one per original UserAccessEntry row in a consolidated group" + +requirements-completed: [RPT-04] + +duration: 1min +completed: "2026-04-09" +--- + +# Phase 15 Plan 01: Consolidation Data Model Summary + +**LocationInfo + ConsolidatedPermissionEntry records and PermissionConsolidator.Consolidate pure-function merge service using LINQ GroupBy over pipe-delimited composite key** + +## Performance + +- **Duration:** ~1 min +- **Started:** 2026-04-09T09:40:40Z +- **Completed:** 2026-04-09T09:41:37Z +- **Tasks:** 2 +- **Files modified:** 3 + +## Accomplishments + +- LocationInfo positional record extracts five location fields from UserAccessEntry during consolidation +- ConsolidatedPermissionEntry record holds key fields plus computed LocationCount convenience property +- PermissionConsolidator.Consolidate groups a flat UserAccessEntry list by (UserLogin, PermissionLevel, AccessType, GrantedThrough) composite key, returning deterministically ordered consolidated rows +- Empty input short-circuits cleanly to Array.Empty without allocating + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Create LocationInfo and ConsolidatedPermissionEntry model records** - `270329b` (feat) +2. **Task 2: Create PermissionConsolidator static helper** - `440b247` (feat) + +## Files Created/Modified + +- `SharepointToolbox/Core/Models/LocationInfo.cs` - Lightweight record holding five location fields extracted from UserAccessEntry when rows are merged +- `SharepointToolbox/Core/Models/ConsolidatedPermissionEntry.cs` - Consolidated permission record with key fields, Locations list, and computed LocationCount property +- `SharepointToolbox/Core/Helpers/PermissionConsolidator.cs` - Static helper with MakeKey (internal) and Consolidate (public) for pure-function merge logic + +## Decisions Made + +- `MakeKey` declared `internal` (not `private`) so test projects can access it via `[InternalsVisibleTo]` without exposing as public API surface +- LINQ GroupBy+Select pattern used instead of mutable dictionary — consistent with functional style seen elsewhere in the codebase +- Output ordered by UserLogin then PermissionLevel for deterministic, predictable export row ordering + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Phase 16 (Report Consolidation Toggle) can now wire PermissionConsolidator.Consolidate into the export pipeline +- All three files exist, compile without errors, and no existing files were modified +- MakeKey is accessible to Phase 16 test projects via InternalsVisibleTo if needed + +--- +*Phase: 15-consolidation-data-model* +*Completed: 2026-04-09*