docs(01-03): complete persistence layer plan — ProfileService + SettingsService
- SUMMARY.md: 7 files, 18 tests, write-then-replace pattern documented - STATE.md: plan 03 complete, progress 38%, decisions recorded - ROADMAP.md: phase 1 progress updated (3/8 plans done) - REQUIREMENTS.md: FOUND-02, FOUND-10, FOUND-12 marked complete
This commit is contained in:
@@ -10,7 +10,7 @@ Requirements for initial release. Each maps to roadmap phases.
|
|||||||
### Foundation
|
### Foundation
|
||||||
|
|
||||||
- [x] **FOUND-01**: Application built with C#/WPF (.NET 10 LTS) using MVVM architecture
|
- [x] **FOUND-01**: Application built with C#/WPF (.NET 10 LTS) using MVVM architecture
|
||||||
- [ ] **FOUND-02**: Multi-tenant profile registry — user can create, rename, delete, and switch between tenant profiles (tenant URL, client ID, display name)
|
- [x] **FOUND-02**: Multi-tenant profile registry — user can create, rename, delete, and switch between tenant profiles (tenant URL, client ID, display name)
|
||||||
- [ ] **FOUND-03**: Multi-tenant session caching — user stays authenticated across tenant switches without re-logging in (MSAL token cache per tenant)
|
- [ ] **FOUND-03**: Multi-tenant session caching — user stays authenticated across tenant switches without re-logging in (MSAL token cache per tenant)
|
||||||
- [ ] **FOUND-04**: Interactive Azure AD OAuth login via browser — no client secrets or certificates stored
|
- [ ] **FOUND-04**: Interactive Azure AD OAuth login via browser — no client secrets or certificates stored
|
||||||
- [x] **FOUND-05**: All long-running operations report progress to the UI in real-time
|
- [x] **FOUND-05**: All long-running operations report progress to the UI in real-time
|
||||||
@@ -18,9 +18,9 @@ Requirements for initial release. Each maps to roadmap phases.
|
|||||||
- [x] **FOUND-07**: All errors surface to the user with actionable messages — no silent failures
|
- [x] **FOUND-07**: All errors surface to the user with actionable messages — no silent failures
|
||||||
- [x] **FOUND-08**: Structured logging for diagnostics (Serilog or equivalent)
|
- [x] **FOUND-08**: Structured logging for diagnostics (Serilog or equivalent)
|
||||||
- [ ] **FOUND-09**: Localization system supporting English and French with dynamic language switching
|
- [ ] **FOUND-09**: Localization system supporting English and French with dynamic language switching
|
||||||
- [ ] **FOUND-10**: JSON-based local storage for profiles, settings, and templates (compatible with current app's format for migration)
|
- [x] **FOUND-10**: JSON-based local storage for profiles, settings, and templates (compatible with current app's format for migration)
|
||||||
- [ ] **FOUND-11**: Self-contained single EXE distribution — no .NET runtime dependency for end users
|
- [ ] **FOUND-11**: Self-contained single EXE distribution — no .NET runtime dependency for end users
|
||||||
- [ ] **FOUND-12**: Configurable data output folder for exports
|
- [x] **FOUND-12**: Configurable data output folder for exports
|
||||||
|
|
||||||
### Permissions
|
### Permissions
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|||||||
| Requirement | Phase | Status |
|
| Requirement | Phase | Status |
|
||||||
|-------------|-------|--------|
|
|-------------|-------|--------|
|
||||||
| FOUND-01 | Phase 1 | Complete |
|
| FOUND-01 | Phase 1 | Complete |
|
||||||
| FOUND-02 | Phase 1 | Pending |
|
| FOUND-02 | Phase 1 | Complete |
|
||||||
| FOUND-03 | Phase 1 | Pending |
|
| FOUND-03 | Phase 1 | Pending |
|
||||||
| FOUND-04 | Phase 1 | Pending |
|
| FOUND-04 | Phase 1 | Pending |
|
||||||
| FOUND-05 | Phase 1 | Complete |
|
| FOUND-05 | Phase 1 | Complete |
|
||||||
@@ -124,9 +124,9 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|||||||
| FOUND-07 | Phase 1 | Complete |
|
| FOUND-07 | Phase 1 | Complete |
|
||||||
| FOUND-08 | Phase 1 | Complete |
|
| FOUND-08 | Phase 1 | Complete |
|
||||||
| FOUND-09 | Phase 1 | Pending |
|
| FOUND-09 | Phase 1 | Pending |
|
||||||
| FOUND-10 | Phase 1 | Pending |
|
| FOUND-10 | Phase 1 | Complete |
|
||||||
| FOUND-11 | Phase 5 | Pending |
|
| FOUND-11 | Phase 5 | Pending |
|
||||||
| FOUND-12 | Phase 1 | Pending |
|
| FOUND-12 | Phase 1 | Complete |
|
||||||
| PERM-01 | Phase 2 | Pending |
|
| PERM-01 | Phase 2 | Pending |
|
||||||
| PERM-02 | Phase 2 | Pending |
|
| PERM-02 | Phase 2 | Pending |
|
||||||
| PERM-03 | Phase 2 | Pending |
|
| PERM-03 | Phase 2 | Pending |
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
|
|||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Foundation | 2/8 | In Progress| |
|
| 1. Foundation | 3/8 | In Progress| |
|
||||||
| 2. Permissions | 0/? | Not started | - |
|
| 2. Permissions | 0/? | Not started | - |
|
||||||
| 3. Storage and File Operations | 0/? | Not started | - |
|
| 3. Storage and File Operations | 0/? | Not started | - |
|
||||||
| 4. Bulk Operations and Provisioning | 0/? | Not started | - |
|
| 4. Bulk Operations and Provisioning | 0/? | Not started | - |
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: planning
|
status: planning
|
||||||
stopped_at: Completed 01-foundation-02-PLAN.md
|
stopped_at: Completed 01-foundation-03-PLAN.md
|
||||||
last_updated: "2026-04-02T10:07:59.501Z"
|
last_updated: "2026-04-02T10:13:20.259Z"
|
||||||
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
||||||
progress:
|
progress:
|
||||||
total_phases: 5
|
total_phases: 5
|
||||||
completed_phases: 0
|
completed_phases: 0
|
||||||
total_plans: 8
|
total_plans: 8
|
||||||
completed_plans: 2
|
completed_plans: 3
|
||||||
percent: 13
|
percent: 13
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ Progress: [█░░░░░░░░░] 13%
|
|||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
| Phase 01-foundation P01 | 4 | 2 tasks | 14 files |
|
| Phase 01-foundation P01 | 4 | 2 tasks | 14 files |
|
||||||
| Phase 01-foundation P02 | 1 | 2 tasks | 7 files |
|
| Phase 01-foundation P02 | 1 | 2 tasks | 7 files |
|
||||||
|
| Phase 01-foundation P03 | 8 | 2 tasks | 7 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -69,6 +70,9 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 01-foundation]: Solution uses .slnx format (new .NET 10 XML solution) — dotnet new sln creates .slnx by default in .NET 10 SDK
|
- [Phase 01-foundation]: Solution uses .slnx format (new .NET 10 XML solution) — dotnet new sln creates .slnx by default in .NET 10 SDK
|
||||||
- [Phase 01-foundation]: TenantProfile is a plain mutable class (not record) — System.Text.Json requires settable properties; field names Name/TenantUrl/ClientId match JSON schema exactly
|
- [Phase 01-foundation]: TenantProfile is a plain mutable class (not record) — System.Text.Json requires settable properties; field names Name/TenantUrl/ClientId match JSON schema exactly
|
||||||
- [Phase 01-foundation]: SharePointPaginationHelper uses [EnumeratorCancellation] on ct — required for correct WithCancellation() forwarding in async iterators
|
- [Phase 01-foundation]: SharePointPaginationHelper uses [EnumeratorCancellation] on ct — required for correct WithCancellation() forwarding in async iterators
|
||||||
|
- [Phase 01-foundation]: Explicit System.IO using required in WPF project — WPF temp build project does not include System.IO in implicit usings; all persistence classes need explicit import
|
||||||
|
- [Phase 01-foundation]: SettingsService validates only 'en' and 'fr' language codes — throws ArgumentException for unsupported codes
|
||||||
|
- [Phase 01-foundation]: LoadAsync on corrupt JSON throws InvalidDataException (not silent empty) — explicit failure protects against silent data loss
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -82,6 +86,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-02T10:07:59.499Z
|
Last session: 2026-04-02T10:13:20.255Z
|
||||||
Stopped at: Completed 01-foundation-02-PLAN.md
|
Stopped at: Completed 01-foundation-03-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
142
.planning/phases/01-foundation/01-03-SUMMARY.md
Normal file
142
.planning/phases/01-foundation/01-03-SUMMARY.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
phase: 01-foundation
|
||||||
|
plan: 03
|
||||||
|
subsystem: persistence
|
||||||
|
tags: [dotnet10, csharp, system-text-json, semaphoreslim, write-then-replace, unit-tests, xunit]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- 01-01 (solution scaffold, test project)
|
||||||
|
- 01-02 (TenantProfile model)
|
||||||
|
provides:
|
||||||
|
- ProfileRepository: file I/O for profiles JSON with SemaphoreSlim write lock and write-then-replace
|
||||||
|
- ProfileService: CRUD (GetProfiles/AddProfile/RenameProfile/DeleteProfile) with input validation
|
||||||
|
- SettingsRepository: file I/O for settings JSON with same write-then-replace safety pattern
|
||||||
|
- SettingsService: GetSettings/SetLanguage/SetDataFolder with supported-language validation
|
||||||
|
- AppSettings model: DataFolder + Lang with camelCase JSON compatibility
|
||||||
|
affects:
|
||||||
|
- 01-04 (MsalClientFactory may use ProfileService for tenant list)
|
||||||
|
- 01-05 (TranslationSource uses SettingsService for lang)
|
||||||
|
- 01-06 (FeatureViewModelBase may use ProfileService/SettingsService)
|
||||||
|
- all feature plans (profile and settings are the core data contracts)
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- Write-then-replace: write to .tmp, validate JSON round-trip via JsonDocument.Parse, then File.Move(overwrite:true)
|
||||||
|
- SemaphoreSlim(1,1) for async exclusive write access on per-repository basis
|
||||||
|
- System.Text.Json with PropertyNamingPolicy.CamelCase for schema-compatible serialization
|
||||||
|
- PropertyNameCaseInsensitive=true for deserialization to handle both old and new JSON
|
||||||
|
- TDD with IDisposable temp file pattern for isolated unit tests
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- SharepointToolbox/Core/Models/AppSettings.cs
|
||||||
|
- SharepointToolbox/Infrastructure/Persistence/ProfileRepository.cs
|
||||||
|
- SharepointToolbox/Infrastructure/Persistence/SettingsRepository.cs
|
||||||
|
- SharepointToolbox/Services/ProfileService.cs
|
||||||
|
- SharepointToolbox/Services/SettingsService.cs
|
||||||
|
- SharepointToolbox.Tests/Services/ProfileServiceTests.cs
|
||||||
|
- SharepointToolbox.Tests/Services/SettingsServiceTests.cs
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Explicit System.IO using required in WPF project — WPF temp build project does not include System.IO in implicit usings; all file I/O classes need explicit namespace import"
|
||||||
|
- "SettingsService validates only 'en' and 'fr' — matches app's supported locales; throws ArgumentException for any other code"
|
||||||
|
- "LoadAsync on corrupt JSON throws InvalidDataException (not silent empty) — explicit failure is safer than silently discarding user data"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Write-then-replace: all file persistence uses .tmp write + JsonDocument.Parse validation + File.Move(overwrite:true) to protect against crash-corruption"
|
||||||
|
- "IDisposable test pattern: unit tests use Path.GetTempFileName() + Dispose() for clean isolated file I/O tests"
|
||||||
|
|
||||||
|
requirements-completed:
|
||||||
|
- FOUND-02
|
||||||
|
- FOUND-10
|
||||||
|
- FOUND-12
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 8min
|
||||||
|
completed: 2026-04-02
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 1 Plan 03: Persistence Layer Summary
|
||||||
|
|
||||||
|
**ProfileRepository + SettingsRepository with write-then-replace safety, ProfileService + SettingsService with validation, 18 unit tests covering round-trips, corrupt-file recovery, concurrency, and JSON schema compatibility**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 8 min
|
||||||
|
- **Started:** 2026-04-02T10:09:13Z
|
||||||
|
- **Completed:** 2026-04-02T10:17:00Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 7
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- ProfileRepository and SettingsRepository both implement write-then-replace (tmp file → JSON validation → File.Move) with SemaphoreSlim(1,1) preventing concurrent write corruption
|
||||||
|
- JSON serialization uses camelCase (PropertyNamingPolicy.CamelCase) — preserves existing user data field names: `profiles`, `name`, `tenantUrl`, `clientId`, `dataFolder`, `lang`
|
||||||
|
- ProfileService provides full CRUD with input validation (Name not empty, TenantUrl valid absolute URL, ClientId not empty)
|
||||||
|
- SettingsService validates language codes against supported set (en/fr only), allows empty dataFolder
|
||||||
|
- All 18 unit tests pass (10 ProfileServiceTests + 8 SettingsServiceTests); no skips
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: ProfileRepository and ProfileService with write-then-replace** - `769196d` (feat)
|
||||||
|
2. **Task 2: SettingsRepository and SettingsService** - `ac3fa5c` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** (docs commit follows)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `SharepointToolbox/Core/Models/AppSettings.cs` - AppSettings model; DataFolder + Lang with camelCase JSON
|
||||||
|
- `SharepointToolbox/Infrastructure/Persistence/ProfileRepository.cs` - File I/O; SemaphoreSlim; write-then-replace; camelCase
|
||||||
|
- `SharepointToolbox/Infrastructure/Persistence/SettingsRepository.cs` - Same pattern as ProfileRepository for settings
|
||||||
|
- `SharepointToolbox/Services/ProfileService.cs` - CRUD on profiles; validates Name/TenantUrl/ClientId; throws KeyNotFoundException
|
||||||
|
- `SharepointToolbox/Services/SettingsService.cs` - Get/SetLanguage/SetDataFolder; validates language codes
|
||||||
|
- `SharepointToolbox.Tests/Services/ProfileServiceTests.cs` - 10 tests: round-trip, missing file, corrupt JSON, concurrency, schema keys
|
||||||
|
- `SharepointToolbox.Tests/Services/SettingsServiceTests.cs` - 8 tests: defaults, round-trip, JSON keys, tmp file, language/folder persistence
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- Explicit `using System.IO;` required in WPF main project — the WPF temp build project does not include `System.IO` in its implicit usings, unlike the standard non-WPF SDK. All repositories need explicit namespace imports.
|
||||||
|
- `SettingsService.SetLanguageAsync` validates only "en" and "fr" using a case-insensitive `HashSet<string>`. Other codes throw `ArgumentException` immediately.
|
||||||
|
- `LoadAsync` on corrupt JSON throws `InvalidDataException` (not silent empty list/default) — this is an explicit safety decision: silently discarding corrupt data could mask accidental overwrites.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] Added explicit System.IO using to WPF project files**
|
||||||
|
- **Found during:** Task 1 (dotnet test — first GREEN attempt)
|
||||||
|
- **Issue:** WPF temporary build project does not include `System.IO` in its implicit usings. `File`, `Path`, `Directory`, `IOException`, `InvalidDataException` all unresolved in the main project and test project.
|
||||||
|
- **Fix:** Added `using System.IO;` at the top of ProfileRepository.cs, SettingsRepository.cs, ProfileServiceTests.cs, and SettingsServiceTests.cs
|
||||||
|
- **Files modified:** All 4 implementation and test files
|
||||||
|
- **Verification:** Build succeeded with 0 errors, 18/18 tests pass
|
||||||
|
- **Committed in:** 769196d and ac3fa5c (inline with respective task commits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 1 auto-fixed (Rule 3 — blocking build issue)
|
||||||
|
**Impact on plan:** One-line fix per file, no logic changes, no scope creep.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None beyond the auto-fixed deviation above.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- ProfileService and SettingsService ready for injection in plan 01-04 (MsalClientFactory may need tenant list from ProfileService)
|
||||||
|
- SettingsService.SetLanguageAsync ready for TranslationSource in plan 01-05
|
||||||
|
- Both services follow the same constructor injection pattern — ready for DI container registration in plan 01-06 or 01-07
|
||||||
|
- JSON schema contracts locked: field names are tested and verified camelCase
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 01-foundation*
|
||||||
|
*Completed: 2026-04-02*
|
||||||
Reference in New Issue
Block a user