docs(18): create phase plan for auto-take-ownership
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
296
.planning/phases/18-auto-take-ownership/18-01-PLAN.md
Normal file
296
.planning/phases/18-auto-take-ownership/18-01-PLAN.md
Normal file
@@ -0,0 +1,296 @@
|
||||
---
|
||||
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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/18-auto-take-ownership/18-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Existing contracts the executor needs -->
|
||||
|
||||
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<AppSettings> 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
|
||||
<!-- Existing: StackPanel with Language, Data folder, MSP Logo sections -->
|
||||
<!-- ADD: Auto-Take Ownership section with CheckBox after MSP Logo, before StatusMessage -->
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Models + SettingsService + OwnershipElevationService + tests</name>
|
||||
<files>
|
||||
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
|
||||
</files>
|
||||
<behavior>
|
||||
- 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
|
||||
</behavior>
|
||||
<action>
|
||||
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<IOwnershipElevationService, OwnershipElevationService>();` (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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>dotnet build SharepointToolbox.sln && dotnet test SharepointToolbox.Tests --no-build --filter "FullyQualifiedName~OwnershipElevation|FullyQualifiedName~SettingsViewModelOwnership"</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: SettingsViewModel property + SettingsView XAML + localization</name>
|
||||
<files>
|
||||
SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs,
|
||||
SharepointToolbox/Views/Tabs/SettingsView.xaml,
|
||||
SharepointToolbox/Localization/Strings.resx,
|
||||
SharepointToolbox/Localization/Strings.fr.resx
|
||||
</files>
|
||||
<action>
|
||||
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
|
||||
<Separator Margin="0,12" />
|
||||
|
||||
<!-- Auto-Take Ownership -->
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.ownership.title]}" />
|
||||
<CheckBox IsChecked="{Binding AutoTakeOwnership}"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.ownership.auto]}"
|
||||
Margin="0,4,0,0" />
|
||||
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.ownership.description]}"
|
||||
Foreground="#666666" FontSize="11" TextWrapping="Wrap" Margin="20,4,0,0" />
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>dotnet build SharepointToolbox.sln && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~LocaleCompleteness" --no-build</automated>
|
||||
</verify>
|
||||
<done>Settings tab shows "Site Ownership" section with checkbox bound to AutoTakeOwnership, defaults unchecked, French locale keys present, LocaleCompletenessTests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/18-auto-take-ownership/18-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user