P2.1: iOS Schema Versioning Strategy
- Added SCHEMA_VERSION constant and checkSchemaVersion() method in PersistenceController
- Version stored in NSPersistentStore metadata (observability contract, not migration gate)
- CoreData auto-migration remains authoritative; version mismatches logged, not blocked
- Documentation added to ios/Plugin/README.md with migration contract
P2.2: Combined Edge Case Tests
- Added 3 resilience test scenarios to DailyNotificationRecoveryTests.swift:
- test_combined_dst_boundary_duplicate_delivery_cold_start()
- test_combined_rollover_duplicate_delivery_cold_start()
- test_combined_schema_version_cold_start_recovery()
- All tests labeled with @resilience @combined-scenarios comments
- Tests verify idempotency and correctness under combined stressors
P2.3: Android Combined Tests Design
- Created P2.3-DESIGN.md with scope, invariants, and acceptance criteria
- Created P2.3-IMPLEMENTATION-CHECKLIST.md with step-by-step execution plan
- Design ready for implementation to achieve parity with iOS P2.2
Documentation Updates
- Fixed parity matrix: iOS invalid data handling now correctly shows "✅ Recovery tested" with test references
- Updated progress docs (00-STATUS.md, 01-CHANGELOG-WORK.md, 03-TEST-RUNS.md, 04-PARITY-MATRIX.md)
- Updated P2-DESIGN.md to reflect P2.3 scope (Android combined tests)
- Updated SYSTEM_INVARIANTS.md baseline tag references
Baseline Tag
- Created and pushed v1.0.11-p2-complete tag
- Tag represents P2.x completion (schema versioning + combined resilience tests)
All invariants preserved. CI passes. Tests runnable via xcodebuild on macOS.
274 lines
7.6 KiB
Markdown
274 lines
7.6 KiB
Markdown
# P2.1: Schema Versioning Strategy - Implementation Plan
|
|
|
|
**Purpose:** Step-by-step implementation plan for P2.1 schema versioning
|
|
**Status:** Ready for execution
|
|
**Date:** 2025-12-22
|
|
**Estimated Effort:** 4-6 hours
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Add explicit schema versioning to iOS CoreData implementation to achieve parity with Android's Room database versioning. This is a **documentation-first, minimal-code** approach that provides observability without interfering with CoreData's automatic migration.
|
|
|
|
---
|
|
|
|
## Implementation Steps
|
|
|
|
### Step 1: Add Schema Version Constant (5 min)
|
|
|
|
**File:** `ios/Plugin/DailyNotificationModel.swift`
|
|
|
|
**Location:** Add near top of `PersistenceController` class
|
|
|
|
**Code:**
|
|
```swift
|
|
class PersistenceController {
|
|
// MARK: - Schema Versioning
|
|
|
|
/// Current schema version (incremented when schema changes)
|
|
private static let SCHEMA_VERSION = 1
|
|
|
|
// ... existing code ...
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] Constant added
|
|
- [ ] Compiles without errors
|
|
|
|
---
|
|
|
|
### Step 2: Add Version Check Method (15 min)
|
|
|
|
**File:** `ios/Plugin/DailyNotificationModel.swift`
|
|
|
|
**Location:** Add as private method in `PersistenceController` class
|
|
|
|
**Code:**
|
|
```swift
|
|
/**
|
|
* Check and log schema version
|
|
*
|
|
* Schema version is a logical contract, not a forced migration trigger.
|
|
* CoreData auto-migration remains authoritative; version mismatches are
|
|
* logged, not blocked.
|
|
*/
|
|
private func checkSchemaVersion() {
|
|
guard let store = container?.persistentStoreCoordinator.persistentStores.first else {
|
|
return
|
|
}
|
|
|
|
let currentVersion = store.metadata["schema_version"] as? Int ?? 1
|
|
let expectedVersion = PersistenceController.SCHEMA_VERSION
|
|
|
|
if currentVersion != expectedVersion {
|
|
print("DNP-PLUGIN: Schema version mismatch - current: \(currentVersion), expected: \(expectedVersion)")
|
|
print("DNP-PLUGIN: CoreData auto-migration will handle schema changes")
|
|
|
|
// Update metadata for future reference (does not trigger migration)
|
|
var metadata = store.metadata
|
|
metadata["schema_version"] = expectedVersion
|
|
// Note: Metadata persists on next store save
|
|
} else {
|
|
print("DNP-PLUGIN: Schema version verified: \(currentVersion)")
|
|
}
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] Method added
|
|
- [ ] Compiles without errors
|
|
- [ ] Follows existing code style
|
|
|
|
---
|
|
|
|
### Step 3: Call Version Check on Initialization (5 min)
|
|
|
|
**File:** `ios/Plugin/DailyNotificationModel.swift`
|
|
|
|
**Location:** In `init(inMemory:)` method, after container is successfully loaded
|
|
|
|
**Code:**
|
|
```swift
|
|
// Configure view context
|
|
if let context = tempContainer?.viewContext {
|
|
context.automaticallyMergesChangesFromParent = true
|
|
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
}
|
|
self.container = tempContainer
|
|
|
|
// Check schema version (after container is initialized)
|
|
checkSchemaVersion()
|
|
|
|
// Verify all entities are available (after container is initialized)
|
|
if let context = tempContainer?.viewContext {
|
|
verifyEntities(in: context)
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] Version check called after container initialization
|
|
- [ ] Compiles without errors
|
|
- [ ] Version logged on app launch
|
|
|
|
---
|
|
|
|
### Step 4: Set Initial Version Metadata (10 min)
|
|
|
|
**File:** `ios/Plugin/DailyNotificationModel.swift`
|
|
|
|
**Location:** In `init(inMemory:)` method, when creating new store
|
|
|
|
**Code:**
|
|
```swift
|
|
// Configure persistent store options
|
|
let description = tempContainer?.persistentStoreDescriptions.first
|
|
description?.shouldMigrateStoreAutomatically = true
|
|
description?.shouldInferMappingModelAutomatically = true
|
|
|
|
// Set initial schema version metadata (for new stores)
|
|
if !inMemory {
|
|
var metadata = description?.metadata ?? [:]
|
|
if metadata["schema_version"] == nil {
|
|
metadata["schema_version"] = PersistenceController.SCHEMA_VERSION
|
|
description?.metadata = metadata
|
|
}
|
|
}
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] Initial version metadata set for new stores
|
|
- [ ] Compiles without errors
|
|
- [ ] Version metadata persists across app restarts
|
|
|
|
---
|
|
|
|
### Step 5: Add Documentation to README (30 min)
|
|
|
|
**File:** `ios/Plugin/README.md`
|
|
|
|
**Location:** Add new section after "Implementation Details" section
|
|
|
|
**Content:** Use the draft from `docs/progress/P2.1-SCHEMA-VERSIONING-DRAFT.md`
|
|
|
|
**Steps:**
|
|
1. Copy the "Schema Versioning Strategy" section from the draft
|
|
2. Paste into `ios/Plugin/README.md` after "Implementation Details"
|
|
3. Update "Last Updated" date in README header
|
|
4. Verify markdown formatting
|
|
|
|
**Verification:**
|
|
- [ ] Documentation added
|
|
- [ ] Markdown renders correctly
|
|
- [ ] All links/references are valid
|
|
- [ ] "Last Updated" date updated
|
|
|
|
---
|
|
|
|
### Step 6: Update Parity Matrix (5 min)
|
|
|
|
**File:** `docs/progress/04-PARITY-MATRIX.md`
|
|
|
|
**Location:** Update "Storage & Persistence" section
|
|
|
|
**Change:**
|
|
```markdown
|
|
| Schema versioning | ✅ Room migrations | ✅ Explicit | iOS has explicit version tracking in CoreData metadata |
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] Parity matrix updated
|
|
- [ ] Status changed from "⚠️ Partial" to "✅ Explicit"
|
|
- [ ] Notes section updated
|
|
|
|
---
|
|
|
|
### Step 7: Update Progress Docs (10 min)
|
|
|
|
**Files:**
|
|
- `docs/progress/00-STATUS.md`
|
|
- `docs/progress/01-CHANGELOG-WORK.md`
|
|
- `docs/progress/03-TEST-RUNS.md`
|
|
|
|
**Updates:**
|
|
1. Mark P2.1 as complete in status
|
|
2. Add changelog entry
|
|
3. Add test run entry (manual verification)
|
|
|
|
**Verification:**
|
|
- [ ] All progress docs updated
|
|
- [ ] Dates are correct
|
|
- [ ] Status reflects completion
|
|
|
|
---
|
|
|
|
### Step 8: Run CI and Verify (10 min)
|
|
|
|
**Command:**
|
|
```bash
|
|
./ci/run.sh
|
|
```
|
|
|
|
**Verification:**
|
|
- [ ] CI passes
|
|
- [ ] No new errors introduced
|
|
- [ ] Version logging appears in console output (manual check)
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
### Manual Testing
|
|
|
|
- [ ] **New Store:** Create new CoreData store, verify version metadata is set
|
|
- [ ] **Existing Store:** Load existing store, verify version check runs
|
|
- [ ] **Version Logging:** Verify version logged on app launch
|
|
- [ ] **Metadata Persistence:** Verify version metadata persists across app restarts
|
|
|
|
### Code Review
|
|
|
|
- [ ] Code follows existing style
|
|
- [ ] Comments are clear and accurate
|
|
- [ ] No breaking changes introduced
|
|
- [ ] CoreData auto-migration still works
|
|
|
|
---
|
|
|
|
## Acceptance Criteria Checklist
|
|
|
|
- [ ] iOS schema versioning strategy documented (with explicit "logical contract" clarification)
|
|
- [ ] Version tracking implemented in CoreData model (metadata)
|
|
- [ ] Migration contract defined (when to bump versions)
|
|
- [ ] Version check utility added (logs version on init, does not block)
|
|
- [ ] Parity matrix updated (schema versioning: ✅ Explicit)
|
|
- [ ] All CI checks pass
|
|
- [ ] Progress docs updated
|
|
|
|
---
|
|
|
|
## Rollback Plan
|
|
|
|
If issues arise:
|
|
|
|
1. **Revert code changes:** Remove version check method and calls
|
|
2. **Revert documentation:** Remove schema versioning section from README
|
|
3. **Revert parity matrix:** Change back to "⚠️ Partial"
|
|
4. **Update progress docs:** Mark P2.1 as incomplete
|
|
|
|
**Baseline tag available:** `v1.0.11-p0-p1.4-p1.5-p2.6-p2.7-complete`
|
|
|
|
---
|
|
|
|
## Next Steps After P2.1
|
|
|
|
1. **Checkpoint:** Run `./ci/run.sh`, update progress docs
|
|
2. **Proceed to P2.2:** Combined edge case tests
|
|
3. **Optional:** Create baseline tag `v1.0.11-p2.1-complete` if desired
|
|
|
|
---
|
|
|
|
**Last Updated:** 2025-12-22
|
|
**Status:** Ready for execution
|
|
|