--- phase: 04-bulk-operations-and-provisioning verified: 2026-04-03T00:00:00Z status: human_needed score: 12/12 must-haves verified human_verification: - test: "Run the application and verify all 5 new tabs are visible and load without crashing" expected: "10 tabs total — Permissions, Storage, Search, Duplicates, Transfer, Bulk Members, Bulk Sites, Folder Structure, Templates, Settings" why_human: "WPF UI startup cannot be verified programmatically" - test: "Bulk Members tab — click Load Example, verify DataGrid populates with sample member rows" expected: "7 rows appear with GroupName, Email, Role columns and all IsValid = True" why_human: "Embedded-resource loading and DataGrid binding require runtime" - test: "Bulk Sites tab — click Load Example, verify DataGrid populates with site rows including semicolon-delimited data" expected: "5 rows appear with Name, Alias, Type, Owners columns parsed correctly" why_human: "Requires runtime CSV parsing with auto-detected semicolon delimiter" - test: "Bulk Members or Sites — import a CSV with one invalid row, verify the invalid row is visible in the DataGrid with an error message in the Errors column" expected: "Valid column shows False, Errors column shows the specific validation message (e.g. 'Invalid email format')" why_human: "DataGrid rendering of CsvValidationRow requires runtime" - test: "Transfer tab — click Browse on Source, verify SitePickerDialog opens; after selecting a site, verify FolderBrowserDialog opens for library/folder selection" expected: "Two-step dialog flow works, selected library/folder path displayed in the Transfer tab" why_human: "Dialog chaining requires a connected tenant and live UI interaction" - test: "Templates tab — verify 5 capture option checkboxes are visible (Libraries, Folders, Permission Groups, Site Logo, Site Settings)" expected: "All 5 checkboxes shown, all checked by default" why_human: "XAML checkbox rendering requires runtime" - test: "On any bulk operation tab, click Execute after loading a CSV, verify the confirmation dialog appears before the operation starts" expected: "ConfirmBulkOperationDialog shows with a summary message and Proceed/Cancel buttons" why_human: "Requires connected tenant or a mock; ShowConfirmDialog is wired through code-behind factory" --- # Phase 4: Bulk Operations and Provisioning — Verification Report **Phase Goal:** Users can execute bulk write operations (member additions, site creation, file transfer) with per-item error reporting and cancellation, capture site structures as reusable templates, apply templates to create new sites, and provision folder structures from CSV — all without silent partial failures. **Verified:** 2026-04-03 **Status:** human_needed — All automated checks passed; 7 items require live UI or connected-tenant verification. **Re-verification:** No — initial verification. --- ## Goal Achievement ### Observable Truths | # | Truth | Status | Evidence | |---|-------|--------|----------| | 1 | Bulk write operations continue on error and report per-item results | VERIFIED | `BulkOperationRunner.RunAsync` catches per-item exceptions, wraps in `BulkItemResult.Failed`, continues loop. 5 unit tests pass including `RunAsync_SomeItemsFail_ContinuesAndReportsPerItem`. | | 2 | Cancellation propagates immediately and stops processing | VERIFIED | `OperationCanceledException` is re-thrown from `BulkOperationRunner.RunAsync`. Tests `RunAsync_Cancelled_ThrowsOperationCanceled` and `RunAsync_CancelledMidOperation_StopsProcessing` pass. Cancel button wired in all Views via `CancelCommand`. | | 3 | File transfer (copy and move) works with per-file error reporting and conflict policies | VERIFIED | `FileTransferService` uses `MoveCopyUtil.CopyFileByPath` / `MoveFileByPath` with `ResourcePath.FromDecodedUrl`, delegates to `BulkOperationRunner.RunAsync`. All three conflict policies (Skip/Overwrite/Rename) implemented via `MoveCopyOptions`. | | 4 | Bulk member addition uses Graph API for M365 groups with CSOM fallback | VERIFIED | `BulkMemberService.AddMembersAsync` delegates to `BulkOperationRunner.RunAsync`. Graph path uses `GraphClientFactory`+`MsalTokenProvider`. CSOM path uses `EnsureUser` + `SiteGroups`. clientId passed explicitly from ViewModel. | | 5 | Bulk site creation creates Team and Communication sites with per-site error reporting | VERIFIED | `BulkSiteService` uses `TeamSiteCollectionCreationInformation` and `CommunicationSiteCollectionCreationInformation` via PnP Framework `CreateSiteAsync`. Delegated to `BulkOperationRunner.RunAsync`. | | 6 | Site structures are captured as reusable templates and persisted locally | VERIFIED | `TemplateService.CaptureTemplateAsync` reads libraries (filtering hidden+system lists), folders (recursive), permission groups, logo, settings via CSOM. `TemplateRepository.SaveAsync` persists JSON with atomic tmp+Move write. 6 TemplateRepository tests pass. | | 7 | Templates can be applied to create new sites with the captured structure | VERIFIED | `TemplateService.ApplyTemplateAsync` creates Team or Communication site via PnP Framework, then recreates libraries, folders (recursive), permission groups via CSOM. Key link to `CaptureTemplateAsync` / `ApplyTemplateAsync` in `TemplatesViewModel` confirmed. | | 8 | Folder structures can be provisioned from CSV with parent-first ordering | VERIFIED | `FolderStructureService.BuildUniquePaths` sorts paths by depth. `CreateFoldersAsync` uses `BulkOperationRunner.RunAsync`. Tests `BuildUniquePaths_FromExampleCsv_ReturnsParentFirst` and `BuildUniquePaths_DuplicateRows_Deduplicated` pass. | | 9 | CSV validation reports per-row errors before execution | VERIFIED | `CsvValidationService` uses CsvHelper with `DetectDelimiter=true`, BOM detection, per-row validation. 9 unit tests pass. DataGrid binds to `CsvValidationRow.IsValid` and `Errors` columns. | | 10 | Failed items can be exported as CSV after partial failures | VERIFIED | `BulkResultCsvExportService.BuildFailedItemsCsv` writes failed-only rows with Error+Timestamp columns. `ExportFailedCommand` wired in all 4 bulk operation ViewModels. 2 unit tests pass. | | 11 | Retry Failed button re-runs only the failed items | VERIFIED | `RetryFailedCommand` in `BulkMembersViewModel` and `BulkSitesViewModel` populates `_failedRowsForRetry` from `_lastResult.FailedItems` and re-runs. Button bound in XAML for both tabs. | | 12 | All 5 new tabs are registered in DI and wired to MainWindow | VERIFIED | All 5 services+ViewModels+Views registered in `App.xaml.cs` (lines 124-152). All 5 TabItems declared in `MainWindow.xaml` with named `x:Name`. Content set from DI in `MainWindow.xaml.cs` (lines 36-40). | **Score:** 12/12 truths verified --- ## Required Artifacts | Artifact | Expected | Status | Details | |----------|----------|--------|---------| | `SharepointToolbox/Services/BulkOperationRunner.cs` | Shared bulk helper with continue-on-error | VERIFIED | `RunAsync` with `OperationCanceledException` re-throw and per-item catch | | `SharepointToolbox/Core/Models/BulkOperationResult.cs` | Per-item result tracking | VERIFIED | `BulkItemResult` with `Success`/`Failed` factories; `BulkOperationSummary` with `HasFailures`, `FailedItems` | | `SharepointToolbox/Core/Models/SiteTemplate.cs` | Template JSON model | VERIFIED | `SiteTemplate`, `TemplateSettings`, `TemplateLogo` classes present | | `SharepointToolbox/Services/CsvValidationService.cs` | CSV parsing + validation | VERIFIED | CsvHelper with `DetectDelimiter`, BOM, per-row member/site/folder validation | | `SharepointToolbox/Infrastructure/Persistence/TemplateRepository.cs` | JSON persistence for templates | VERIFIED | `SemaphoreSlim`, atomic tmp+Move write, `JsonSerializer`, full CRUD | | `SharepointToolbox/Services/FileTransferService.cs` | CSOM file transfer | VERIFIED | `MoveCopyUtil.CopyFileByPath`/`MoveFileByPath`, `ResourcePath.FromDecodedUrl`, 3 conflict policies | | `SharepointToolbox/Services/BulkMemberService.cs` | Graph + CSOM member addition | VERIFIED | Graph SDK path + CSOM fallback, delegates to `BulkOperationRunner.RunAsync` | | `SharepointToolbox/Infrastructure/Auth/GraphClientFactory.cs` | Graph SDK client from MSAL | VERIFIED | `MsalTokenProvider` bridges MSAL PCA to `BaseBearerTokenAuthenticationProvider` | | `SharepointToolbox/Services/BulkSiteService.cs` | Bulk site creation | VERIFIED | Team + Communication site creation via PnP Framework, `BulkOperationRunner.RunAsync` | | `SharepointToolbox/Services/TemplateService.cs` | Site template capture + apply | VERIFIED | `SystemListNames` filter, recursive folder enumeration, permission group capture, apply creates site + recreates structure | | `SharepointToolbox/Services/FolderStructureService.cs` | Folder creation from CSV | VERIFIED | `BuildUniquePaths` parent-first sort, `BulkOperationRunner.RunAsync`, `Web.Folders.Add` | | `SharepointToolbox/Services/Export/BulkResultCsvExportService.cs` | Failed items CSV export | VERIFIED | `CsvWriter` with `WriteHeader` + Error + Timestamp columns | | `SharepointToolbox/Views/Dialogs/ConfirmBulkOperationDialog.xaml` | Pre-write confirmation dialog | VERIFIED | Proceed/Cancel buttons, `IsConfirmed` property, `TranslationSource` bindings | | `SharepointToolbox/Views/Dialogs/FolderBrowserDialog.xaml` | Library/folder tree browser | VERIFIED | `TreeView` with lazy-load expansion, library load on `Loaded` event | | `SharepointToolbox/Resources/bulk_add_members.csv` | Example CSV — members | VERIFIED | Present as `EmbeddedResource` in csproj | | `SharepointToolbox/Resources/bulk_create_sites.csv` | Example CSV — sites | VERIFIED | Present as `EmbeddedResource` in csproj | | `SharepointToolbox/Resources/folder_structure.csv` | Example CSV — folder structure | VERIFIED | Present as `EmbeddedResource` in csproj | | `SharepointToolbox/ViewModels/Tabs/TransferViewModel.cs` | Transfer tab ViewModel | VERIFIED | `TransferAsync` called, `GetOrCreateContextAsync` for both contexts, `ExportFailedCommand` | | `SharepointToolbox/ViewModels/Tabs/BulkMembersViewModel.cs` | Bulk Members ViewModel | VERIFIED | `ParseAndValidateMembers`, `AddMembersAsync`, `RetryFailedCommand`, `ExportFailedCommand`, `LoadExampleCommand` | | `SharepointToolbox/ViewModels/Tabs/BulkSitesViewModel.cs` | Bulk Sites ViewModel | VERIFIED | `ParseAndValidateSites`, `CreateSitesAsync`, `RetryFailedCommand`, `ExportFailedCommand`, `LoadExampleCommand` | | `SharepointToolbox/ViewModels/Tabs/FolderStructureViewModel.cs` | Folder Structure ViewModel | VERIFIED | `ParseAndValidateFolders`, `CreateFoldersAsync`, `BuildUniquePaths` called, `ExportFailedCommand` | | `SharepointToolbox/ViewModels/Tabs/TemplatesViewModel.cs` | Templates ViewModel | VERIFIED | `CaptureTemplateAsync`, `ApplyTemplateAsync`, `TemplateRepository` CRUD, `RefreshCommand` | | `SharepointToolbox/Views/Tabs/TransferView.xaml` | Transfer tab UI | VERIFIED | Source/dest site pickers, library/folder browse buttons, Copy/Move radio, conflict policy, progress, ExportFailed button | | `SharepointToolbox/Views/Tabs/BulkMembersView.xaml` | Bulk Members tab UI | VERIFIED | Import/LoadExample buttons, DataGrid with IsValid+Errors columns, RunCommand, RetryFailed, ExportFailed | | `SharepointToolbox/Views/Tabs/BulkSitesView.xaml` | Bulk Sites tab UI | VERIFIED | Same pattern as BulkMembers | | `SharepointToolbox/Views/Tabs/FolderStructureView.xaml` | Folder Structure tab UI | VERIFIED | DataGrid with Level1-4 columns and Errors column | | `SharepointToolbox/Views/Tabs/TemplatesView.xaml` | Templates tab UI | VERIFIED | Capture section with 5 checkboxes, Apply section with title/alias, template DataGrid | | `SharepointToolbox/App.xaml.cs` | DI registration for all Phase 4 types | VERIFIED | Lines 124-152: all 5 services, ViewModels, Views registered | | `SharepointToolbox/MainWindow.xaml` | 5 new tab items | VERIFIED | TransferTabItem, BulkMembersTabItem, BulkSitesTabItem, FolderStructureTabItem, TemplatesTabItem | --- ## Key Link Verification | From | To | Via | Status | Details | |------|----|-----|--------|---------| | `BulkOperationRunner.cs` | `BulkOperationResult.cs` | returns `BulkOperationSummary` | WIRED | `return new BulkOperationSummary(results)` on line 34 | | `FileTransferService.cs` | `BulkOperationRunner.cs` | per-file delegation | WIRED | `BulkOperationRunner.RunAsync` called on line 33 | | `FileTransferService.cs` | `MoveCopyUtil` | CSOM file operations | WIRED | `MoveCopyUtil.CopyFileByPath` (line 85), `MoveCopyUtil.MoveFileByPath` (line 90) | | `BulkMemberService.cs` | `BulkOperationRunner.cs` | per-row delegation | WIRED | `BulkOperationRunner.RunAsync` on line 28 | | `GraphClientFactory.cs` | `MsalClientFactory` | shared MSAL token | WIRED | `_msalFactory.GetOrCreateClient(clientId)` in `CreateClientAsync` | | `BulkSiteService.cs` | `BulkOperationRunner.cs` | per-site delegation | WIRED | `BulkOperationRunner.RunAsync` on line 17 | | `TemplateService.cs` | `SiteTemplate.cs` | builds and returns model | WIRED | `SiteTemplate` constructed in `CaptureTemplateAsync`, pattern confirmed | | `FolderStructureService.cs` | `BulkOperationRunner.cs` | per-folder error handling | WIRED | `BulkOperationRunner.RunAsync` on line 27 | | `CsvValidationService.cs` | `CsvHelper` | CsvReader with DetectDelimiter | WIRED | `CsvReader` with `DetectDelimiter = true` and `detectEncodingFromByteOrderMarks: true` | | `TemplateRepository.cs` | `SiteTemplate.cs` | System.Text.Json serialization | WIRED | `JsonSerializer.Serialize/Deserialize` | | `TransferViewModel.cs` | `IFileTransferService.TransferAsync` | RunOperationAsync override | WIRED | `_transferService.TransferAsync(srcCtx, dstCtx, job, progress, ct)` on line 109 | | `TransferViewModel.cs` | `ISessionManager.GetOrCreateContextAsync` | context acquisition | WIRED | Called for both srcProfile and dstProfile on lines 106-107 | | `BulkMembersView.xaml.cs` | `TranslationSource` | localized labels | WIRED | All buttons use `TranslationSource.Instance` binding | | `TemplatesViewModel.cs` | `ITemplateService` | capture and apply | WIRED | `_templateService.CaptureTemplateAsync` (line 112), `ApplyTemplateAsync` (line 148) | | `TemplatesViewModel.cs` | `TemplateRepository` | template CRUD | WIRED | `_templateRepo.SaveAsync`, `RenameAsync`, `DeleteAsync`, `GetAllAsync` all called | | `App.xaml.cs` | All Phase 4 services | DI registration | WIRED | `AddTransient`/`AddSingleton` for all 10 Phase 4 service types (lines 124-152) | | `MainWindow.xaml.cs` | All Phase 4 Views | tab content wiring | WIRED | `GetRequiredService()` lines 36-40 | | `ConfirmBulkOperationDialog.xaml.cs` | `TranslationSource` | localized button text | WIRED | Title and button text bound to `bulk.confirm.*` keys | --- ## Requirements Coverage | Requirement | Source Plan | Description | Status | Evidence | |-------------|------------|-------------|--------|----------| | BULK-01 | 04-03, 04-08 | File/folder transfer between sites with progress tracking | SATISFIED | `FileTransferService` + `TransferViewModel` + `TransferView`. Copy/Move modes, progress bar, cancel, per-file results. | | BULK-02 | 04-04, 04-09 | Bulk member addition from CSV | SATISFIED | `BulkMemberService` (Graph + CSOM) + `BulkMembersViewModel` (CSV import, preview, execute, retry, export) | | BULK-03 | 04-05, 04-09 | Bulk site creation from CSV | SATISFIED | `BulkSiteService` (Team + Communication) + `BulkSitesViewModel` (CSV import, preview, execute) | | BULK-04 | 04-01, 04-03, 04-04, 04-05, 04-06, 04-08, 04-09 | All bulk operations support cancellation | SATISFIED | `BulkOperationRunner.RunAsync` propagates `OperationCanceledException`. Cancel button wired in all 4 Views. | | BULK-05 | 04-01, 04-03, 04-04, 04-05, 04-06, 04-08, 04-09 | Per-item error reporting (no silent failures) | SATISFIED | `BulkItemResult.Failed` per item. `HasFailures`/`FailedItems` exposed. ExportFailed + RetryFailed in all Views. | | TMPL-01 | 04-06, 04-10 | Capture site structure as template | SATISFIED | `TemplateService.CaptureTemplateAsync` captures libraries (filtered), folders (recursive), groups, logo, settings per `SiteTemplateOptions` | | TMPL-02 | 04-06, 04-10 | Apply template to create new site | SATISFIED | `TemplateService.ApplyTemplateAsync` creates Team or Communication site, recreates structure | | TMPL-03 | 04-02 | Templates persist locally as JSON | SATISFIED | `TemplateRepository` with atomic write (tmp + Move), `JsonSerializer`, 6 passing tests | | TMPL-04 | 04-02, 04-10 | Manage templates (create, rename, delete) | SATISFIED | `TemplatesViewModel` has `CaptureCommand`, `RenameCommand`, `DeleteCommand`. `TemplateRepository` has full CRUD. | | FOLD-01 | 04-06, 04-09 | Folder structure creation from CSV | SATISFIED | `FolderStructureService.CreateFoldersAsync` with parent-first ordering. `FolderStructureViewModel` with CSV import, preview, execute. | | FOLD-02 | 04-07, 04-09 | Example CSV templates provided | SATISFIED | 3 example CSVs in `Resources/` as `EmbeddedResource`. `LoadExampleCommand` in all 3 CSV ViewModels reads from embedded assembly. | All 11 requirement IDs accounted for. No orphaned requirements. --- ## Anti-Patterns Found | File | Observation | Severity | Impact | |------|-------------|----------|--------| | `BulkMembersView.xaml` | DataGrid shows IsValid as text column ("True"/"False") but no row-level visual highlighting (no `DataGrid.RowStyle` + `DataTrigger` for red background on invalid rows) | Warning | Invalid rows are identifiable via column text, but visually indistinct. Fix requires adding `RowStyle` with `DataTrigger IsValid=False -> Background=LightCoral`. | | `BulkSitesView.xaml` | Same as above — no row highlighting for invalid rows | Warning | Same impact | | `FolderStructureView.xaml` | Same as above | Warning | Same impact | No blocker anti-patterns. No TODO/FIXME/placeholder comments in service or ViewModel files. No `throw new NotImplementedException`. All services have real implementations. --- ## Human Verification Required ### 1. Application Launch with 5 New Tabs **Test:** Run `dotnet run --project SharepointToolbox/SharepointToolbox.csproj` and count tabs in MainWindow **Expected:** 10 visible tabs: Permissions, Storage, Search, Duplicates, Transfer, Bulk Members, Bulk Sites, Folder Structure, Templates, Settings **Why human:** WPF application startup, DI resolution, and XAML rendering cannot be verified programmatically ### 2. Bulk Members — Load Example Flow **Test:** Click the "Load Example" button on the Bulk Members tab **Expected:** DataGrid populates with 7 sample rows (all IsValid = True). PreviewSummary shows "7 rows, 7 valid, 0 invalid" **Why human:** Requires embedded resource loading, CsvHelper parsing, and DataGrid MVVM binding at runtime ### 3. Bulk Sites — Semicolon CSV Auto-Detection **Test:** Click "Load Example" on Bulk Sites tab **Expected:** 5 rows parsed correctly despite semicolon delimiter. Name, Alias, Type columns show correct values. **Why human:** DetectDelimiter behavior requires runtime CsvHelper parsing ### 4. Invalid Row Display in DataGrid **Test:** Import a CSV with one invalid row (e.g., missing email) to Bulk Members **Expected:** Invalid row visible, IsValid column shows "False", Errors column shows the specific error message **Why human:** DataGrid rendering requires runtime ### 5. Confirmation Dialog Before Execution **Test:** Load a valid CSV on Bulk Members and click "Add Members" **Expected:** `ConfirmBulkOperationDialog` appears with operation summary and Proceed/Cancel buttons before any SharePoint call is made **Why human:** Requires ShowConfirmDialog factory to fire via code-behind at runtime ### 6. Transfer Tab — Two-Step Browse Flow **Test:** On Transfer tab, click Browse for Source; complete the SitePickerDialog; observe FolderBrowserDialog opens **Expected:** After selecting a site in SitePickerDialog, FolderBrowserDialog opens and loads document libraries from that site **Why human:** Requires connected tenant and live dialog interaction ### 7. Templates Tab — Capture Checkboxes **Test:** Navigate to Templates tab **Expected:** Capture section shows 5 checkboxes (Libraries, Folders, Permission Groups, Site Logo, Site Settings), all checked by default **Why human:** XAML checkbox default state and layout require runtime rendering --- ## Build and Test Summary **Build:** `dotnet build SharepointToolbox.slnx` — Build succeeded, 0 errors **Tests:** 122 passed, 22 skipped (all skipped tests require live SharePoint tenant — correctly marked), 0 failed **Key test results:** - BulkOperationRunner: 5/5 pass (all semantics verified including continue-on-error and cancellation) - CsvValidationService: 9/9 pass (comma + semicolon delimiters, BOM, member/site/folder validation) - TemplateRepository: 6/6 pass (round-trip JSON, GetAll, Delete, Rename) - FolderStructureService: 4/5 pass + 1 skip (BuildUniquePaths logic verified; live SharePoint test skipped) - BulkResultCsvExportService: 2/2 pass (failed-only filtering, Error+Timestamp columns) --- _Verified: 2026-04-03T00:00:00Z_ _Verifier: Claude (gsd-verifier)_