Repository navigation
feat(ios): hydrate recipe bundle-v2 catalog with legacy fallback and revision history - #151
obvious-autobuild[bot] wants to merge 13 commits into
Conversation
Register four subsystem migration files (RecipeCatalog, Taste, CookSession, Community) after the core v18 migration so parallel branches can add local tables without editing Migrations.swift. Registration order is fixed because shipped databases record applied identifiers and subsystem migrations may reference core tables and earlier subsystems. Structural seam tests pin the contract: full migrate, upTo v18 then finish, identifier classification, and block ordering.
applied[.. < firstSubsystem] parses as a comparison, not a half-open range, which failed the CI build. prefix(_:) and suffix(from:) express the same slices without operator spacing pitfalls. Co-authored-by: Obvious Autobuild <agents@obvious.bot>
…seam' into obv/burst5-fridgeluck-rcp-loader
…revision history Adds the recipe-catalog persistence layer: subsystem migrations behind the recipes_ identifier seam, a bundle-v2 loader with semantic validation, and read paths on RecipeRepository. - RecipeCatalogMigrations: recipes_catalog (+ nutrition snapshots, structured lines, structured steps), recipes_catalog_ingredients vocabulary; thermometer columns move as all-or-nothing; nutrition history is insert-once per revision. - RecipeBundleV2Loader: applies recipes-v2.json when the bundle ships it, else hydrates legacy data.json into catalog rows keyed rcp-NNNNNN by legacy id (revision 1, quarantined rights, grams-only lines). v2 recipes claim their legacy successor only when the numeric suffix names a bundled recipe with a matching title, so cooking history keeps resolving without false links. Idempotent on re-apply; user-edited rows never overwritten; historical nutrition preserved byte-for-byte across revision bumps. - RecipeRepositoryCatalog: catalogRecipe/catalogRecipes reads, legacy-id bridge, and per-revision nutrition snapshots. - AppDatabase warmup applies the catalog after core hydration. - RecipeLoaderTests: fresh install, v18 upgrade with cooking history, idempotence, revision bump, user-edit survival, loud failure naming the recipe id (malformed recipe, unpinned thermometer source, wrong bundle version), and read-path coverage against the spec example bundle embedded verbatim. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…alidation Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…e synthesizes Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
- Open RecipeRepository.db to internal so repository extensions in separate files can read the queue. - Add the missing raw: argument label and try on the throwing vocabulary lookup in the legacy hydration path. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…elpers Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…w migration - The legacy loader stamps legacy_recipe_id, which FK-points at recipes(id); tests that apply the legacy JSON now seed matching recipes rows first, mirroring the production upgrade state. - The migration-order test now expects recipes_catalog_v1 as the fourth migration. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
- A quarantined fallback row (rights_status = quarantined with a legacy link) is a stand-in until structured content lands, so a bundle recipe now supersedes it even at the same revision. The legacy link survives only when the title-and-source rule genuinely matches; preserving it blindly misattributed cooking history for rcp-000101. - Capture the quarantined revision between the legacy and bundle applies in the v18 upgrade test, and seed the legacy recipes rows the FK requires in the two fresh-database legacy tests (an earlier same-file edit race dropped these calls from the pushed commit). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
RecipeCatalogRightsStatus is a top-level type in RecipeBundleV2.swift, not nested under RecipeCatalogRights. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
|
Independent review (Obvious, Autobuild + Max) Verdict: ready. No blocking defect found. This draft was reviewed independently of its authorship; I executed the decode/validate layer locally (Swift 6.4 on Linux, byte-identical PR source), traced every loader/repository claim in the diff against repo sources and production Findings (ranked by severity)
What I verified (commands and results)
What I could not verify
|
What changed and why
Implements the recipe-catalog persistence layer from the bundle-v2 contract (
docs/spec/recipes/), on top of the migration seam fromobv/burst5-fridgeluck-migration-seam:RecipeCatalogMigrationsfills the empty seam:recipes_catalog(one row per stablecatalog_id, current revision summary, nullablelegacy_recipe_idlink,is_user_editedguard),recipes_catalog_ingredientsvocabulary (keyed by canonical string key,fdc_id/legacy_idas evidence links, nullableper_100g), per-revisionrecipes_catalog_nutritionsnapshots (insert-once, never rewritten), structuredrecipes_catalog_lines(quantity evidence: grams / count / household / unknown) andrecipes_catalog_steps(timers, thermometer checks — the three thermometer columns move as all-or-nothing via CHECK). Nothing joins on integer ids; legacyrecipesandcooking_historyare never rewritten.RecipeBundleV2Loaderhydrates at launch (wired intoAppDatabase.warmupafter core hydration). It appliesrecipes-v2.jsonwhen the bundle ships it, else falls back to legacydata.json, hydrating it into catalog rows keyedrcp-NNNNNNby the legacy integer id (revision 1, quarantined rights — legacy rows predate the rights audit and carry no licence evidence, so they stay display-only — and grams-only lines; steps stay empty and legacy rows keep rendering through the existing instructions path). A v2 recipe claims its legacy successor only when the numericrcp-NNNNNNsuffix names a bundled recipe (id 1–166,source = 'bundled') with the same title — conservative, sorcp-000101(Coconut Chickpea Curry) does not grab legacy recipe 101 (Moroccan chicken stew) whilercp-000002does succeed the omelette and cooking history keeps resolving. Applies are idempotent: same-or-older revision is a no-op, user-edited rows are never overwritten, and per-revision nutrition history survives revision bumps byte-for-byte.RecipeBundleV2Loaderfallback semantics (added during CI rounds): a quarantined legacy fallback row is a stand-in until structured content lands, so a bundle recipe supersedes it even at the same revision; the fallback's legacy link survives only when the title-and-source successor rule genuinely matches, so quarantined rows never leak a wronglegacy_recipe_idinto a replaced recipe.RecipeRepositoryCatalogadds read paths:catalogRecipe/catalogRecipes, the legacy-id → catalog-id bridge for cooking history, and per-revision nutrition snapshots.RecipeLoaderTests(newTests/RecipeLoader/target dir, picked up by the existingpath: Teststest target): fresh install, v18 upgrade with cooking history, idempotence, revision bump, user-edit survival, loud failure naming the recipe id (malformed recipe, unpinned thermometer source, wrong bundle version), and read-path coverage. The fixture is the spec's example bundle (docs/spec/recipes/contracts/examples/bundle-v2.sample.json) embedded verbatim. The v18-upgrade and legacy-fallback tests seed therecipesrows thelegacy_recipe_idforeign key requires.How I checked it
72e5d96(run 38731987703, build-and-test): the full test suite compiles and passes on the Apple toolchain, including allRecipeLoaderTests. Earlier runs on this PR caught and fixed, in order: an invalidwhere letloop clause (2da715a), a Codable synthesis failure on theingredientscoding key (743cd75), repository access and API-label issues (d41c59c), test-helper typing issues (12d7d95,d2a73c3), unseeded legacy foreign-key rows plus a missing migration-id assertion (01ba691), lost seed calls from a same-file edit race plus quarantined-fallback supersession semantics (2ccba13), and theRecipeCatalogRightsStatustype reference (72e5d96)."revision": 2,"servings": 1,"uses": [0, 2],usda-fsis: ground beef 160 F,"bundle_version": 2) was confirmed unique in the fixture so the mutations only touch the intended recipe."""or"#sequences in it.DatabaseMigrations.migrate(_:upTo:)and thev18_cooking_history_swapsid,bundled_recipe_state(v8),recipe_ingredients/cooking_historycolumn orders for the seeded rows,RecipeTagsbit positions,BundledData/IngredientArray/RecipeArrayfield names and sodium-in-grams semantics (×1000 → mg),RecipeRepository/NutritionService/HealthScoringService/PersonalizationServiceinitializers, andEquatableconformance on every model type used inXCTAssertEqual.What I could NOT check
swiftc, and installing one risks the 6.7 GB free disk), so nothing was built or run locally — but the repository's hosted macOS CI at72e5d96compiles the code and runs the full test suite green (run 38731987703), which supersedes the earlier "uncompiled" caveat for type-level and test correctness.recipes-v2.jsonbundle shipped through an Xcode target.bundleV2URL(in:)(RecipeCatalog/subdirectory) are untested until an Xcode target actually ships the resource; until then the legacy fallback path is what runs.Assumptions
obv/burst5-fridgeluck-spec-recipes(the branch this work branches from), notmain, matching the spec-series flow.rights_status = 'quarantined') rather than guessed at: they predate the rights audit, so they are display-only and excluded from sharing until provenance is established.bundled_recipe_statemarker keyrecipes_catalog_v2_markerrecords bundle identity (size+mtime); it is informational (re-apply decisions are revision-driven), kept for parity with the existing v7/v8 marker pattern.