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.
160 lines
5.2 KiB
Markdown
160 lines
5.2 KiB
Markdown
# P2.1: Schema Versioning Strategy - Documentation Draft
|
|
|
|
**Purpose:** Draft documentation for iOS schema versioning strategy (ready to integrate into `ios/Plugin/README.md`)
|
|
**Status:** Draft for review
|
|
**Date:** 2025-12-22
|
|
|
|
---
|
|
|
|
## Section to Add to `ios/Plugin/README.md`
|
|
|
|
### Schema Versioning Strategy
|
|
|
|
**Current Schema Version:** `1` (initial schema)
|
|
|
|
The iOS implementation uses **explicit schema versioning** to achieve parity with Android's Room database versioning approach. This provides observability and migration tracking without interfering with CoreData's automatic migration capabilities.
|
|
|
|
#### Versioning Approach
|
|
|
|
**CoreData Auto-Migration Remains Authoritative**
|
|
|
|
The schema version is a **logical contract**, not a forced migration trigger. CoreData auto-migration (`shouldMigrateStoreAutomatically = true`) remains the authoritative mechanism for schema changes. Version mismatches are **logged, not blocked**.
|
|
|
|
**Version Tracking**
|
|
|
|
Schema version is stored in CoreData persistent store metadata using `NSPersistentStore` metadata dictionary. This approach:
|
|
|
|
- ✅ Non-intrusive (does not require schema changes)
|
|
- ✅ Observable (version can be read at any time)
|
|
- ✅ Compatible with CoreData auto-migration
|
|
- ✅ Matches Android's explicit versioning pattern
|
|
|
|
**Current Implementation**
|
|
|
|
- **Schema Version:** `1` (initial schema, established 2025-09-22)
|
|
- **Version Storage:** `NSPersistentStore` metadata key `"schema_version"`
|
|
- **Version Check:** Performed during `PersistenceController` initialization
|
|
- **Logging:** Version logged on store load; mismatches logged as warnings
|
|
|
|
#### Migration Contract
|
|
|
|
**When to Bump Schema Version**
|
|
|
|
The schema version should be incremented when:
|
|
|
|
1. **Entity changes:**
|
|
- Adding new entities
|
|
- Removing entities (rare, requires data migration)
|
|
- Renaming entities (requires explicit migration)
|
|
|
|
2. **Attribute changes:**
|
|
- Adding new required attributes (requires default values or migration)
|
|
- Removing attributes (requires data cleanup)
|
|
- Changing attribute types (requires type conversion)
|
|
- Renaming attributes (requires explicit migration)
|
|
|
|
3. **Relationship changes:**
|
|
- Adding/removing relationships
|
|
- Changing relationship cardinality
|
|
- Renaming relationships
|
|
|
|
**When NOT to Bump**
|
|
|
|
- Adding optional attributes (CoreData handles automatically)
|
|
- Adding optional relationships (CoreData handles automatically)
|
|
- Changing default values (no schema change required)
|
|
- Adding indexes (metadata change, not schema change)
|
|
|
|
**Version Bump Process**
|
|
|
|
1. Update CoreData model in Xcode (add/remove/modify entities/attributes)
|
|
2. Increment schema version constant in `PersistenceController`
|
|
3. Update metadata on next store load
|
|
4. Document migration in changelog
|
|
5. Update parity matrix if versioning strategy changes
|
|
|
|
#### Android Parity
|
|
|
|
**Android:** Room database with explicit `version = 2` and `Migration` objects
|
|
**iOS:** CoreData with explicit schema version `1` in metadata + auto-migration
|
|
|
|
Both platforms now have:
|
|
- ✅ Explicit version tracking
|
|
- ✅ Migration documentation
|
|
- ✅ Version observability
|
|
- ✅ Migration contract defined
|
|
|
|
**Parity Status:** ✅ **Explicit versioning** (P2.1 complete)
|
|
|
|
---
|
|
|
|
## Implementation Notes
|
|
|
|
### Version Check Utility
|
|
|
|
A simple version check is performed during `PersistenceController` initialization:
|
|
|
|
```swift
|
|
// In PersistenceController.init()
|
|
private func checkSchemaVersion() {
|
|
guard let store = container?.persistentStoreCoordinator.persistentStores.first else {
|
|
return
|
|
}
|
|
|
|
let currentVersion = store.metadata["schema_version"] as? Int ?? 1
|
|
let expectedVersion = SCHEMA_VERSION
|
|
|
|
if currentVersion != expectedVersion {
|
|
print("DNP-PLUGIN: Schema version mismatch - current: \(currentVersion), expected: \(expectedVersion)")
|
|
// Log warning, but do not block (CoreData auto-migration handles actual migration)
|
|
} else {
|
|
print("DNP-PLUGIN: Schema version verified: \(currentVersion)")
|
|
}
|
|
|
|
// Update metadata if needed
|
|
if currentVersion != expectedVersion {
|
|
var metadata = store.metadata
|
|
metadata["schema_version"] = expectedVersion
|
|
// Note: Metadata update happens on next store save
|
|
}
|
|
}
|
|
```
|
|
|
|
### Constants
|
|
|
|
```swift
|
|
// In PersistenceController
|
|
private static let SCHEMA_VERSION = 1 // Current schema version
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
Version handling is verified through:
|
|
|
|
1. **Unit tests:** Verify version metadata is set correctly
|
|
2. **Integration tests:** Verify version check runs on store load
|
|
3. **Migration tests:** Verify version tracking survives migrations
|
|
|
|
**Test Coverage:**
|
|
- ✅ Version metadata is set on initial store creation
|
|
- ✅ Version check runs during initialization
|
|
- ✅ Version mismatches are logged (not blocked)
|
|
- ✅ Version metadata persists across app restarts
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- **Android Schema Versioning:** `android/src/main/java/com/timesafari/dailynotification/DatabaseSchema.kt` (Room `version = 2`)
|
|
- **CoreData Model:** `ios/Plugin/DailyNotificationModel.xcdatamodeld`
|
|
- **PersistenceController:** `ios/Plugin/DailyNotificationModel.swift`
|
|
- **Parity Matrix:** `docs/progress/04-PARITY-MATRIX.md`
|
|
|
|
---
|
|
|
|
**Last Updated:** 2025-12-22
|
|
**Status:** Draft for integration
|
|
|