--- phase: 18-auto-take-ownership plan: 01 type: execute wave: 1 depends_on: [] files_modified: - SharepointToolbox/Core/Models/AppSettings.cs - SharepointToolbox/Core/Models/PermissionEntry.cs - SharepointToolbox/Services/SettingsService.cs - SharepointToolbox/Services/IOwnershipElevationService.cs - SharepointToolbox/Services/OwnershipElevationService.cs - SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs - SharepointToolbox/Views/Tabs/SettingsView.xaml - SharepointToolbox/Localization/Strings.resx - SharepointToolbox/Localization/Strings.fr.resx - SharepointToolbox/App.xaml.cs - SharepointToolbox.Tests/Services/OwnershipElevationServiceTests.cs - SharepointToolbox.Tests/ViewModels/SettingsViewModelOwnershipTests.cs autonomous: true requirements: - OWN-01 must_haves: truths: - "AutoTakeOwnership defaults to false in AppSettings" - "Setting round-trips through SettingsRepository JSON persistence" - "SettingsViewModel exposes AutoTakeOwnership and persists on toggle" - "Settings UI shows an auto-take-ownership checkbox, OFF by default" - "PermissionEntry.WasAutoElevated exists with default false, zero callsite breakage" - "IOwnershipElevationService contract exists for Tenant.SetSiteAdmin wrapping" artifacts: - path: "SharepointToolbox/Core/Models/AppSettings.cs" provides: "AutoTakeOwnership bool property defaulting to false" contains: "AutoTakeOwnership" - path: "SharepointToolbox/Core/Models/PermissionEntry.cs" provides: "WasAutoElevated flag on PermissionEntry record" contains: "WasAutoElevated" - path: "SharepointToolbox/Services/IOwnershipElevationService.cs" provides: "Elevation service interface" contains: "interface IOwnershipElevationService" - path: "SharepointToolbox/Services/OwnershipElevationService.cs" provides: "Tenant.SetSiteAdmin wrapper" contains: "class OwnershipElevationService" - path: "SharepointToolbox/Services/SettingsService.cs" provides: "SetAutoTakeOwnershipAsync method" contains: "SetAutoTakeOwnershipAsync" - path: "SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs" provides: "AutoTakeOwnership observable property" contains: "AutoTakeOwnership" - path: "SharepointToolbox/Views/Tabs/SettingsView.xaml" provides: "CheckBox for auto-take-ownership toggle" contains: "AutoTakeOwnership" key_links: - from: "SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs" to: "SharepointToolbox/Services/SettingsService.cs" via: "SetAutoTakeOwnershipAsync call on property change" pattern: "SetAutoTakeOwnershipAsync" - from: "SharepointToolbox/Views/Tabs/SettingsView.xaml" to: "SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs" via: "CheckBox IsChecked binding to AutoTakeOwnership" pattern: "AutoTakeOwnership" --- Add the auto-take-ownership settings toggle (OWN-01), the PermissionEntry.WasAutoElevated flag, and the IOwnershipElevationService contract+implementation that wraps Tenant.SetSiteAdmin. Purpose: Establish the data model changes and settings persistence so Plan 02 can wire the scan-loop elevation logic. Output: AppSettings extended, SettingsViewModel wired, SettingsView checkbox visible, OwnershipElevationService ready for injection. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/18-auto-take-ownership/18-RESEARCH.md From SharepointToolbox/Core/Models/AppSettings.cs: ```csharp public class AppSettings { public string DataFolder { get; set; } = string.Empty; public string Lang { get; set; } = "en"; // ADD: public bool AutoTakeOwnership { get; set; } = false; } ``` From SharepointToolbox/Core/Models/PermissionEntry.cs: ```csharp public record PermissionEntry( string ObjectType, string Title, string Url, bool HasUniquePermissions, string Users, string UserLogins, string PermissionLevels, string GrantedThrough, string PrincipalType // ADD: bool WasAutoElevated = false -- MUST be last, with default ); ``` From SharepointToolbox/Services/SettingsService.cs: ```csharp public class SettingsService { public Task GetSettingsAsync(); public async Task SetLanguageAsync(string cultureCode); public async Task SetDataFolderAsync(string path); // ADD: public async Task SetAutoTakeOwnershipAsync(bool enabled); } ``` From SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs: ```csharp public partial class SettingsViewModel : FeatureViewModelBase { // Constructor: SettingsService settingsService, IBrandingService brandingService, ILogger logger // ADD: AutoTakeOwnership property following DataFolder/SelectedLanguage pattern // ADD: Load in LoadAsync(), persist on set via _settingsService.SetAutoTakeOwnershipAsync } ``` From SharepointToolbox/Views/Tabs/SettingsView.xaml: ```xml ``` Task 1: Models + SettingsService + OwnershipElevationService + tests SharepointToolbox/Core/Models/AppSettings.cs, SharepointToolbox/Core/Models/PermissionEntry.cs, SharepointToolbox/Services/SettingsService.cs, SharepointToolbox/Services/IOwnershipElevationService.cs, SharepointToolbox/Services/OwnershipElevationService.cs, SharepointToolbox.Tests/Services/OwnershipElevationServiceTests.cs, SharepointToolbox.Tests/ViewModels/SettingsViewModelOwnershipTests.cs - Test: AppSettings.AutoTakeOwnership defaults to false - Test: AppSettings with AutoTakeOwnership=true round-trips through JSON serialization - Test: SettingsService.SetAutoTakeOwnershipAsync persists the value (load -> set -> load -> verify) - Test: PermissionEntry with no WasAutoElevated arg defaults to false (backward compat) - Test: PermissionEntry with WasAutoElevated=true returns true - Test: PermissionEntry `with { WasAutoElevated = true }` produces correct copy - Test: OwnershipElevationService implements IOwnershipElevationService (type check) - Test: SettingsViewModel.AutoTakeOwnership loads false from default settings - Test: SettingsViewModel.AutoTakeOwnership set to true calls SetAutoTakeOwnershipAsync 1. Add `public bool AutoTakeOwnership { get; set; } = false;` to `AppSettings.cs`. 2. Append `bool WasAutoElevated = false` as the LAST positional parameter in the `PermissionEntry` record. Must be last with default to avoid breaking existing callsites. 3. Add `SetAutoTakeOwnershipAsync(bool enabled)` to `SettingsService.cs` following the exact pattern of `SetLanguageAsync`: ```csharp public async Task SetAutoTakeOwnershipAsync(bool enabled) { var settings = await _repository.LoadAsync(); settings.AutoTakeOwnership = enabled; await _repository.SaveAsync(settings); } ``` 4. Create `Services/IOwnershipElevationService.cs`: ```csharp public interface IOwnershipElevationService { Task ElevateAsync(ClientContext tenantAdminCtx, string siteUrl, string loginName, CancellationToken ct); } ``` 5. Create `Services/OwnershipElevationService.cs`: ```csharp using Microsoft.Online.SharePoint.TenantAdministration; public class OwnershipElevationService : IOwnershipElevationService { public async Task ElevateAsync(ClientContext tenantAdminCtx, string siteUrl, string loginName, CancellationToken ct) { var tenant = new Tenant(tenantAdminCtx); tenant.SetSiteAdmin(siteUrl, loginName, isSiteAdmin: true); await tenantAdminCtx.ExecuteQueryAsync(); } } ``` 6. Register in `App.xaml.cs` DI: `services.AddTransient();` (place near the ISharePointGroupResolver registration). 7. Create test files: - `OwnershipElevationServiceTests.cs`: Type-check test that `OwnershipElevationService` implements `IOwnershipElevationService`. No CSOM mock needed for the interface contract test. - `SettingsViewModelOwnershipTests.cs`: Test that SettingsViewModel loads AutoTakeOwnership from settings and that setting it calls SetAutoTakeOwnershipAsync. Use a mock/fake SettingsService (follow existing test patterns in the test project). 8. Run `dotnet build SharepointToolbox.sln` to confirm zero breakage from PermissionEntry change. All existing callsites use positional args without specifying WasAutoElevated, so the default kicks in. dotnet build SharepointToolbox.sln && dotnet test SharepointToolbox.Tests --no-build --filter "FullyQualifiedName~OwnershipElevation|FullyQualifiedName~SettingsViewModelOwnership" AppSettings has AutoTakeOwnership (default false), PermissionEntry has WasAutoElevated (default false), SettingsService has SetAutoTakeOwnershipAsync, IOwnershipElevationService + OwnershipElevationService exist and are DI-registered, all tests pass, full solution builds with zero errors. Task 2: SettingsViewModel property + SettingsView XAML + localization SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs, SharepointToolbox/Views/Tabs/SettingsView.xaml, SharepointToolbox/Localization/Strings.resx, SharepointToolbox/Localization/Strings.fr.resx 1. In `SettingsViewModel.cs`, add the `AutoTakeOwnership` property following the exact DataFolder pattern: ```csharp private bool _autoTakeOwnership; public bool AutoTakeOwnership { get => _autoTakeOwnership; set { if (_autoTakeOwnership == value) return; _autoTakeOwnership = value; OnPropertyChanged(); _ = _settingsService.SetAutoTakeOwnershipAsync(value); } } ``` 2. In `LoadAsync()`, after loading `_dataFolder`, add: ```csharp _autoTakeOwnership = settings.AutoTakeOwnership; OnPropertyChanged(nameof(AutoTakeOwnership)); ``` 3. Add localization keys to `Strings.resx`: - `settings.ownership.title` = "Site Ownership" - `settings.ownership.auto` = "Automatically take site collection admin ownership on access denied" - `settings.ownership.description` = "When enabled, the app will automatically elevate to site collection admin when a scan encounters an access denied error. Requires Tenant Admin permissions." 4. Add matching French translations to `Strings.fr.resx`: - `settings.ownership.title` = "Propri\u00e9t\u00e9 du site" - `settings.ownership.auto` = "Prendre automatiquement la propri\u00e9t\u00e9 d'administrateur de collection de sites en cas de refus d'acc\u00e8s" - `settings.ownership.description` = "Lorsqu'activ\u00e9, l'application prendra automatiquement les droits d'administrateur de collection de sites lorsqu'un scan rencontre une erreur de refus d'acc\u00e8s. N\u00e9cessite les permissions d'administrateur de tenant." 5. In `SettingsView.xaml`, add a new section AFTER the MSP Logo section (after the logo StackPanel and before the StatusMessage TextBlock): ```xml dotnet build SharepointToolbox.sln && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~LocaleCompleteness" --no-build Settings tab shows "Site Ownership" section with checkbox bound to AutoTakeOwnership, defaults unchecked, French locale keys present, LocaleCompletenessTests pass. 1. `dotnet build SharepointToolbox.sln` — zero errors, zero warnings 2. `dotnet test SharepointToolbox.Tests --no-build` — full suite green (no regressions from PermissionEntry change) 3. `dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~OwnershipElevation|FullyQualifiedName~SettingsViewModelOwnership" --no-build` — new tests pass 4. `dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~LocaleCompleteness" --no-build` — locale keys complete - AppSettings.AutoTakeOwnership exists and defaults to false - PermissionEntry.WasAutoElevated exists with default false, all existing tests still pass - SettingsService.SetAutoTakeOwnershipAsync persists the toggle - IOwnershipElevationService + OwnershipElevationService registered in DI - SettingsViewModel loads and persists AutoTakeOwnership - SettingsView.xaml shows checkbox for auto-take-ownership - All EN + FR localization keys present - Full test suite green After completion, create `.planning/phases/18-auto-take-ownership/18-01-SUMMARY.md`