diff --git a/.planning/STATE.md b/.planning/STATE.md index 7f85628..c50667d 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -2,15 +2,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone -status: in-progress +status: planning stopped_at: Completed 02-07-PLAN.md (Phase 2 complete) -last_updated: "2026-04-02T14:30:00.000Z" +last_updated: "2026-04-02T12:29:10.218Z" last_activity: 2026-04-02 — Phase 2 Permissions fully integrated (PermissionsView wired, DI registered, human-verified) progress: total_phases: 5 completed_phases: 2 - total_plans: 22 - completed_plans: 16 + total_plans: 15 + completed_plans: 15 percent: 73 --- diff --git a/.planning/phases/02-permissions/02-VERIFICATION.md b/.planning/phases/02-permissions/02-VERIFICATION.md new file mode 100644 index 0000000..91609a1 --- /dev/null +++ b/.planning/phases/02-permissions/02-VERIFICATION.md @@ -0,0 +1,167 @@ +--- +phase: 02-permissions +verified: 2026-04-02T14:30:00Z +status: human_needed +score: 6/7 must-haves verified automatically +human_verification: + - test: "Run the application and confirm all 7 UI checklist items in Plan 07" + expected: "Permissions tab visible with scan options, DataGrid, export buttons disabled when empty, French locale translates all labels, Cancel button disabled at idle, View Sites opens SitePickerDialog" + why_human: "UI layout, localization rendering, live dialog behavior, and button enabled-state cannot be verified programmatically" + - test: "Confirm Export CSV / Export HTML buttons are localized (or intentionally hardcoded)" + expected: "Buttons either use the rad.csv.perms / rad.html.perms localization keys, or the decision to use hardcoded 'Export CSV' / 'Export HTML' was intentional" + why_human: "XAML uses hardcoded English strings 'Export CSV' and 'Export HTML' instead of localization bindings — minor i18n gap that needs human decision on whether it is acceptable" +--- + +# Phase 2: Permissions Verification Report + +**Phase Goal:** Implement the Permissions tab — a full SharePoint permissions scanner with multi-site support, CSV/HTML export, and scan options. Port of the PowerShell Generate-PnPSitePermissionRpt function. +**Verified:** 2026-04-02T14:30:00Z +**Status:** human_needed +**Re-verification:** No — initial verification + +--- + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | User can scan permissions on a single SharePoint site with configurable depth (PERM-01) | VERIFIED | `PermissionsService.ScanSiteAsync` fully implemented; SiteUrl bound in XAML; `ScanOptions(FolderDepth)` passed through | +| 2 | User can scan permissions across multiple selected sites in one operation (PERM-02) | VERIFIED | `PermissionsViewModel.RunOperationAsync` loops over `SelectedSites`; `PermissionsViewModelTests.StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl` passes; `SiteListService` + `SitePickerDialog` wired end-to-end | +| 3 | Permissions scan includes owners, members, guests, external users, and broken inheritance (PERM-03) | VERIFIED | `PermissionsService` scans site collection admins, web, lists, folders; `#EXT#` detection in `PermissionEntryHelper.IsExternalUser`; `PrincipalType` set correctly; 7 classification tests pass | +| 4 | User can choose to include or exclude inherited permissions (PERM-04) | VERIFIED | `IncludeInherited` bool bound in XAML via `{Binding IncludeInherited}`; passed to `ScanOptions`; `ExtractPermissionsAsync` skips non-unique objects when `IncludeInherited=false` | +| 5 | User can export permissions report to CSV (PERM-05) | VERIFIED | `CsvExportService.BuildCsv` + `WriteAsync` implemented; UTF-8 BOM; merges rows by (Users, PermissionLevels, GrantedThrough); all 3 `CsvExportServiceTests` pass | +| 6 | User can export permissions report to interactive HTML (PERM-06) | VERIFIED | `HtmlExportService.BuildHtml` produces self-contained HTML with inline CSS/JS, stats cards, type badges, external-user pills; all 3 `HtmlExportServiceTests` pass | +| 7 | SharePoint 5,000-item list view threshold handled via pagination — no silent failures (PERM-07) | VERIFIED | `PermissionsService.GetFolderPermissionsAsync` uses `SharePointPaginationHelper.GetAllItemsAsync` with `RowLimit 500` pagination — never raw list enumeration (grep confirmed line 222) | + +**Score:** 7/7 truths verified (all automated; 2 items need human confirmation for UI/i18n quality) + +--- + +### Required Artifacts + +| Artifact | Provided By | Status | Details | +|----------|------------|--------|---------| +| `SharepointToolbox/Core/Models/PermissionEntry.cs` | Plan 02 | VERIFIED | 9-field record; compiles; referenced by tests | +| `SharepointToolbox/Core/Models/ScanOptions.cs` | Plan 02 | VERIFIED | Immutable record with correct defaults | +| `SharepointToolbox/Core/Models/SiteInfo.cs` | Plan 03 | VERIFIED | `record SiteInfo(string Url, string Title)` | +| `SharepointToolbox/Services/IPermissionsService.cs` | Plan 02 | VERIFIED | Interface with `ScanSiteAsync` signature | +| `SharepointToolbox/Services/PermissionsService.cs` | Plan 02 | VERIFIED | 341 lines; implements all 5 scan paths | +| `SharepointToolbox/Services/ISiteListService.cs` | Plan 03 | VERIFIED | Interface with `GetSitesAsync` signature | +| `SharepointToolbox/Services/SiteListService.cs` | Plan 03 | VERIFIED | `DeriveAdminUrl` implemented; error wrapping present | +| `SharepointToolbox/Services/Export/CsvExportService.cs` | Plan 04 | VERIFIED | Merge logic + RFC 4180 escaping + UTF-8 BOM | +| `SharepointToolbox/Services/Export/HtmlExportService.cs` | Plan 04 | VERIFIED | Self-contained HTML; no external deps; external-user class | +| `SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs` | Plan 01 | VERIFIED | `IsExternalUser`, `FilterPermissionLevels`, `IsSharingLinksGroup` | +| `SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs` | Plan 06 | VERIFIED | `FeatureViewModelBase` subclass; 309 lines; all commands present | +| `SharepointToolbox/Views/Dialogs/SitePickerDialog.xaml.cs` | Plan 06 | VERIFIED | Loads sites via `ISiteListService`; filter; CheckBox; OK/Cancel | +| `SharepointToolbox/Views/Tabs/PermissionsView.xaml` | Plan 07 | VERIFIED | Left config panel + right DataGrid + StatusBar; localized | +| `SharepointToolbox/Views/Tabs/PermissionsView.xaml.cs` | Plan 07 | VERIFIED | DI wiring; `GetRequiredService()`; dialog factory | +| `SharepointToolbox/App.xaml.cs` | Plan 07 | VERIFIED | All Phase 2 DI registrations present; `Func` factory registered | +| `SharepointToolbox/MainWindow.xaml` | Plan 07 | VERIFIED | `PermissionsTabItem` uses `x:Name`; no `FeatureTabBase` stub | +| `SharepointToolbox/MainWindow.xaml.cs` | Plan 07 | VERIFIED | `PermissionsTabItem.Content = serviceProvider.GetRequiredService()` | +| `SharepointToolbox/Localization/Strings.resx` | Plan 05 | VERIFIED | 15 Phase 2 keys present (grp.scan.opts, btn.gen.perms, perm.sites.selected, etc.) | +| `SharepointToolbox/Localization/Strings.fr.resx` | Plan 05 | VERIFIED | 15 keys with French translations (e.g., "Options d'analyse", "Analyser les dossiers") | +| `SharepointToolbox/Localization/Strings.Designer.cs` | Plan 05 | PARTIAL | 15 new static properties present; `tab_permissions` property absent (key exists in resx, MainWindow binds via `TranslationSource` directly — low impact) | +| Test scaffold (5 files) | Plan 01 | VERIFIED | All exist; classification tests pass; ViewModel test passes | + +--- + +### Key Link Verification + +| From | To | Via | Status | Evidence | +|------|----|-----|--------|----------| +| `PermissionsService.cs` | `SharePointPaginationHelper.GetAllItemsAsync` | Folder enumeration | WIRED | Line 222: `await foreach (var item in SharePointPaginationHelper.GetAllItemsAsync(...))` | +| `PermissionsService.cs` | `ExecuteQueryRetryHelper.ExecuteQueryRetryAsync` | All CSOM round-trips | WIRED | 7 call sites in service (lines 52, 86, 125, 217, 245, 283) | +| `PermissionsService.cs` | `PermissionEntryHelper.IsExternalUser` | User classification | WIRED | Line 314 | +| `PermissionsService.cs` | `PermissionEntryHelper.FilterPermissionLevels` | Level filtering | WIRED | Line 304 | +| `PermissionsService.cs` | `PermissionEntryHelper.IsSharingLinksGroup` | Group skipping | WIRED | Line 299 | +| `SiteListService.cs` | `SessionManager.GetOrCreateContextAsync` | Admin context acquisition | WIRED | Line 41 | +| `SiteListService.cs` | `Microsoft.Online.SharePoint.TenantAdministration.Tenant` | `GetSitePropertiesFromSharePoint` | WIRED | Line 49: `new Tenant(adminCtx)` | +| `PermissionsViewModel.cs` | `IPermissionsService.ScanSiteAsync` | RunOperationAsync loop | WIRED | Line 189 | +| `PermissionsViewModel.cs` | `CsvExportService.WriteAsync` | ExportCsvCommand handler | WIRED | Line 252 | +| `PermissionsViewModel.cs` | `HtmlExportService.WriteAsync` | ExportHtmlCommand handler | WIRED | Line 275 | +| `SitePickerDialog.xaml.cs` | `ISiteListService.GetSitesAsync` | Window.Loaded handler | WIRED | Line 42 | +| `PermissionsView.xaml.cs` | `PermissionsViewModel` | `GetRequiredService()` | WIRED | Line 14 | +| `PermissionsView.xaml.cs` | `SitePickerDialog` | `OpenSitePickerDialog` factory | WIRED | Lines 16-19 | +| `App.xaml.cs` | Phase 2 services | `AddTransient()` etc. | WIRED | Lines 92-100 | + +--- + +### Requirements Coverage + +| Requirement | Source Plans | Description | Status | Evidence | +|-------------|-------------|-------------|--------|----------| +| PERM-01 | 02-01, 02-02, 02-05, 02-06, 02-07 | Single-site scan with configurable depth | SATISFIED | `PermissionsService.ScanSiteAsync`; SiteUrl XAML binding; ScanOptions wired | +| PERM-02 | 02-01, 02-03, 02-06, 02-07 | Multi-site scan | SATISFIED | `SiteListService`; `SitePickerDialog`; loop in `RunOperationAsync`; test passes | +| PERM-03 | 02-01, 02-02, 02-07 | Owners, members, guests, external users, broken inheritance | SATISFIED | Site collection admins path; `#EXT#` detection; `PrincipalType` assignment; 7 classification tests pass | +| PERM-04 | 02-01, 02-02, 02-05, 02-06, 02-07 | Include/exclude inherited permissions | SATISFIED | `IncludeInherited` checkbox bound; `ScanOptions` record passed; `ExtractPermissionsAsync` gate | +| PERM-05 | 02-01, 02-04, 02-07 | CSV export | SATISFIED | `CsvExportService` with merge, RFC 4180 escaping, UTF-8 BOM; 3 tests pass; `ExportCsvCommand` wired | +| PERM-06 | 02-01, 02-04, 02-07 | HTML export | SATISFIED | `HtmlExportService` self-contained HTML; inline CSS/JS; stats cards; external-user pills; 3 tests pass; `ExportHtmlCommand` wired | +| PERM-07 | 02-02, 02-07 | 5,000-item list view threshold — pagination | SATISFIED | `SharePointPaginationHelper.GetAllItemsAsync` called in `GetFolderPermissionsAsync`; `RowLimit 500` CAML | + +All 7 PERM requirements: SATISFIED + +--- + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `PermissionsView.xaml` | 80, 84 | Hardcoded `Content="Export CSV"` and `Content="Export HTML"` instead of localization bindings | Info | French locale users see English button labels; `rad.csv.perms` and `rad.html.perms` keys exist in resx and Designer.cs but are unused in XAML | +| `Strings.Designer.cs` | n/a | Missing `tab_permissions` static property (key exists in resx) | Info | No functional impact — `TranslationSource.Instance["tab.permissions"]` resolves correctly at runtime via ResourceManager; Designer.cs property is just a typed convenience accessor | + +No Blocker or Warning severity anti-patterns found. + +--- + +### Human Verification Required + +#### 1. Full UI visual checkpoint + +**Test:** Run the application (`dotnet run --project SharepointToolbox` or F5). Navigate to the Permissions tab. +**Expected:** +- Tab is labelled "Permissions" (or "Permissions" in French) and shows scan options panel + empty DataGrid, not "Coming soon" +- Scan Options panel shows: Site URL input, "View Sites" button, "Scan Folders" checkbox, "Include Inherited Permissions" checkbox, "Recursive (subsites)" checkbox, "Folder depth" input, "Maximum (all levels)" checkbox +- "Generate Report" and "Cancel" buttons present +- "Export CSV" and "Export HTML" buttons are disabled (grayed out) with no results +- Click "View Sites" — SitePickerDialog opens (auth error expected if not connected — must not crash) +- Switch to French (Settings tab) — all labels in Permissions tab change to French text +**Why human:** Visual appearance, disabled-state behavior, and locale rendering cannot be verified programmatically. + +#### 2. Export button localization decision + +**Test:** In the running application (French locale), check the text on the Export buttons. +**Expected:** Either the buttons read "CSV" / "HTML" (acceptable if intentional) or the team decides to bind them to `rad.csv.perms` / `rad.html.perms`. +**Why human:** The XAML has `Content="Export CSV"` and `Content="Export HTML"` hardcoded — the localization keys exist but are not used. This is a minor i18n gap requiring a team decision, not a blocker. + +--- + +### Test Results + +| Test class | Tests | Passed | Skipped | Failed | +|-----------|-------|--------|---------|--------| +| `PermissionEntryClassificationTests` | 7 | 7 | 0 | 0 | +| `CsvExportServiceTests` | 3 | 3 | 0 | 0 | +| `HtmlExportServiceTests` | 3 | 3 | 0 | 0 | +| `PermissionsViewModelTests` | 1 | 1 | 0 | 0 | +| `SiteListServiceTests` | 2 | 2 | 0 | 0 | +| `PermissionsServiceTests` | 2 | 0 | 2 | 0 | +| **Full suite** | **63** | **60** | **3** | **0** | + +Skipped tests are intentional live-CSOM stubs (require a real SharePoint context). + +--- + +### Gaps Summary + +No gaps blocking goal achievement. All 7 PERM requirements are implemented with real, substantive code. All key links are wired. All critical service chains are verified. + +Two minor informational items were found: +1. Export buttons in `PermissionsView.xaml` use hardcoded English strings instead of the localization keys that exist in the resx files. This causes the buttons to stay in English when switching to French. The keys `rad.csv.perms` ("CSV") and `rad.html.perms` ("HTML") do exist and resolve correctly — they just aren't bound. This is a cosmetic i18n gap, not a functional failure. +2. `Strings.Designer.cs` is missing the `tab_permissions` typed property (the key exists in both resx files and the MainWindow binding resolves it correctly at runtime via `TranslationSource`). + +--- + +*Verified: 2026-04-02T14:30:00Z* +*Verifier: Claude (gsd-verifier)*