--- title: 'Tech Design: Code-defined Core Data models' created: '2026-09-25' tags: - tech-design - apple - core-data - persistence - spm - build status: draft chosen-approach: 'Schemas defined in Swift with full version history; explicit lightweight migration' asana: TODO --- **Author:** Alexey Martemyanov **Reviewer:** Sam Symons **Stakeholders:** Bartek, Dominik, Federico Cappelli, Michal Smaga **Project:** Build the apps and packages outside Xcode (VS Code / `swift build`) --- ## Background & Requirements We're moving development and CI to VS Code and SwiftPM builds that don't go through Xcode. Core Data schemas live in `.xcdatamodeld` files. Only Xcode compiles them into the `.momd` the app loads, so SwiftPM builds and tests outside Xcode have no model. BSK tests work around this by running `momc` at runtime. Requirements: - existing user stores open unchanged - upgrades from any older version keep data - minimum OS stays at iOS 15 / macOS 12.3. ## Problem Statement Core Data models can't be built outside Xcode. ## Recommended Approach Define each model in Swift instead of `.xcdatamodeld`, one file per model, containing every shipped schema version. A small `Persistence` helper builds the model at runtime and migrates stores created by older versions.[1] Existing stores are unaffected: Core Data matches a store to a model by schema hashes, and the code-defined versions produce the same hashes as the compiled `.momd`. The hashes are pinned in tests. Steps (one PR each): 1. `Persistence` support + PrivacyStats and BrowsingHistory. 2. Bookmarks + SyncMetadata. 3. Stores combining several models: HTTPSUpgrade, RemoteMessaging, AppRatingPrompt.[2] 4. macOS app models: History, Downloads, legacy Bookmark. 5. Remove the `momc` workaround. Each step is revertible: the on-disk format doesn't change. ## Notes [1] Old versions are needed because migration looks for the old schema in the compiled models, which will no longer exist. `NSStagedMigrationManager` handles code-defined versions but requires iOS 17 / macOS 14. [2] A combined store needs the old versions of all its models, so they're converted together. Alternatives: - Remove only the generated dynamic CoreData property accessors: `momc` is still needed and compiled as a separate Swift Build System Plugin step. - Commit precompiled `.momd` files: binary artifacts that can drift from the source. Trade-off: we lose Xcode's visual model editor (but we get standardized, structured, great&shiny code definitions!) ## Testing - Unit tests pin the schema hashes of every shipped version. - Migration tests upgrade old-version stores and check the data. - Package tests run with plain `swift test`. - Manual check on iOS and macOS: upgrade from the App Store build and confirm the data is intact. ## Additional Considerations - **Privacy** — No change. - **Security** — No change. - **Site Breakage** — N/A. - **Experimentation** — N/A. The schema can't be feature-flagged; rollback is a revert. - **Operational** — Package builds no longer need Xcode‘s momd compiler. Migration failures are reported through the existing store-loading errors. - **Localization / Internationalization** — N/A.