Skip to content

Integration trunk (do not merge as-is) - #93

Draft
SamGu-NRX wants to merge 75 commits into
alphafrom
fleet/fl-trunk-20261011
Draft

SamGu-NRX wants to merge 75 commits into
alphafrom
fleet/fl-trunk-20261011

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Integration trunk (do not merge as-is)

App integration trunk for programme fl-integration-f1: folds the app-side fleet PRs into alpha as real --no-ff merges, in dependency-safe order, with per-fold verification. Draft — never merge. Backend PRs fold on the parallel backend trunk PR #94; the two trunks are independent until Sam integrates them.

Base: alpha @ 2e779381bb3939cf35f1948875bc657b7d6cb1ad (fresh-fetched before branching; unchanged).
Branch: fleet/fl-trunk-20261011 · Final head: d17f4cc5fca75e7528d7e57a9ba8c7d948006823

Folds (11 merges, in this order)

# PR Head folded Merge commit
1 #23 fix/intake-inference-semantics 35c358b7d4 e0b8ec7
2 #15 obv/fridgeluck-tour-dismiss-race 5e6000dc75 4dc8b64
3 #28 obv/fridgeluck-013 a915aa7e0a 7392209
4 #35 obv/fridgeluck-042 (brief's "#34" entry specified this branch+SHA explicitly; see ledger) 4eb62954eb 62647e6
5 #36 obv/fridgeluck-046 ebb604704a e30b808
6 #37 obv/fridgeluck-056 4f51d21cd1 fd21677
7 #32 obv/fridgeluck-001 (v19 migration appended, order preserved) c18ece18ea 34db183
8 #33 obv/fridgeluck-040 24fc372bd4 8d58610
9 #6 fix/notification-settings-sendable 854084860c 2a3f79f
10 #18 obv/fridgeluck-054 (workflow delta held → ci/pending-workflows/ios-ci.yml) 51b449ae3f 95ea8ed
11 #31 obv/fridgeluck-033 (merged cleanly; test suite adapted — see CONFLICTS.md) (branch head) eccf587

Trunk fixes on top of the folds

Commit What Why
49d3d0e docs: ledger + conflicts records every fold, verdict, migration order
56b212f hoist v19 migration-test calls out of write closures #32's own head was red (compile); mechanical, intent-preserving fix
8538b94 docs: #32 source-red attribution ledger
a5ac7c7 break up two expressions #37 added that never compiled #37's own head was red (compile); fixes recorded, all audit findings kept
a4c6bf5 replace accessibilityLiveRegion with .accessibilityAddTraits(.updatesFrequently) the SwiftUI API does not exist (it is an Android concept); the iOS equivalent keeps #37's VoiceOver intent
e1cf354 align OnboardingGatePolicyTests fixture with the v19 schema; ledger source-red rows #32's HealthProfile persists two v19 columns the old fixture lacked
d17f4cc extend MigrationUpgradeTests' suffix window to four #32 appended v19 without updating the upgrade-from-v15 expectation (its own CI never reached tests)

Linux verification (SwiftPM, this sandbox)

$ cd apps/ios && swift build && swift test        # Linux, run before every push
swift build exit 0, 0 unique errors (0 new vs alpha baseline; 4 baseline errors resolved by #31's portability guard)
Test Suite 'All tests' passed (incl. InventoryInvariantOperationTests: 16-case invariants + seeded operation-sequence test)

The macOS-only app test target (intake inference, gate policy, migration upgrade, accessibility audits) does not run on Linux; it is exercised by iOS CI below.

macOS iOS CI (real results; runs automatically on this draft PR, build-and-test on macos-26)

Head Result What it showed
49d3d0e (first pushed head) ❌ failure #32 source-red: v19 migration test API misuse (fixed in 56b212f)
8538b94 ❌ failure #37 source-red: accessibilityLiveRegion unresolved + type-check timeout — byte-identical error set to #37's own head CI (fixed in a5ac7c7 + a4c6bf5)
a5ac7c7 ❌ failure compile clean; test failures surfaced: #23's intake suite + #32's gate-policy fixture gap
a4c6bf5 / e1cf354 ❌ failure intake suite (source-red #23) + migration-order expectation gap (fixed in d17f4cc)
d17f4cc (final) ❌ failure only the 8 InventoryIntakeInferenceTests failures remain — 0 compile errors, all other suites green: https://github.com/SamGu-NRX/FridgeLuck/actions/runs/38147643539/job/114494245881

Remaining known red: #23's characterization suite (source-red, recorded — not a merge regression)

PR #23's merge added only its 308-line test file (e0b8ec7); the engine (InventoryIntakeService.swift) is byte-identical between #23's head, alpha, and the trunk. #23's own head CI failed with the same assertions: https://github.com/SamGu-NRX/FridgeLuck/actions/runs/37970992723. The suite characterizes "first storage word wins" semantics the engine never implemented. Folded as-is per the brief's fold list; Sam decides whether the engine semantics or the suite wins. Everything else on macOS is green at d17f4cc.

Migration ledger

v19_explicit_allergen_groups (#32) appends after alpha's v18; no migration was renamed or renumbered. Full order recorded in docs/integration/PR-LEDGER.md.

Workflow hold (#18)

.github/workflows/ on this branch is byte-identical to alpha (git diff 2e779381 HEAD -- .github/workflows/ is empty). #18's proposed workflow is staged at ci/pending-workflows/ios-ci.yml with purpose, trigger, and provenance comments. Only Sam can approve workflow changes.

Not folded (verdicts in the ledger)

#44, #45 (active continuations — pending owner Handoff); #56, #71, #83 (wait on those); #16/#19/#21/#22/#24/#25/#26/#29/#30/#39/#43 → backend trunk PR #94; #17 (superseded by this trunk); research/eval/benchmark PRs; docs-only; CI-only proposals (needs-owner); viable product PRs outside the brief's fold list (needs-owner with fold-candidate notes: #14 #42 #52 #61 #70 #74–#77 #79 #80 #82 #84).

Proof

All commands run in the Obvious Linux sandbox (Swift 6.4) unless noted.

$ git log -1 --format='%h %an <%ae>'            # checked before every push
d17f4cc Sam Gu <127461594+SamGu-NRX@users.noreply.github.com>
$ git log -1 --format='%(trailers:key=Co-authored-by)'   # verified on every commit
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

$ cd apps/ios && swift build                     # Linux
Build complete!                                  (exit 0, 0 unique errors; 0 new vs alpha baseline)
$ swift test                                     # Linux
Test Suite 'All tests' passed                    (exit 0)

$ git diff 2e779381 HEAD -- .github/workflows/ | wc -l
0                                                # workflow hold verified empty

$ git log --oneline --merges | head -11          # 11 real --no-ff folds
eccf587 Merge PR #31 ... / 95ea8ed Merge PR #18 ... / 2a3f79f Merge PR #6 ...
8d58610 Merge PR #33 ... / 34db183 Merge PR #32 ... / fd21677 Merge PR #37 ...
e30b808 Merge PR #36 ... / 62647e6 Merge PR #34(042) ... / 7392209 Merge PR #28 ...
4dc8b64 Merge PR #15 ... / e0b8ec7 Merge PR #23 ...

macOS runs are real CI executions (links in the table above); no native check is claimed verified without a run.

Handoff

Done

Not done / open

Next steps for a continuation

  1. Read docs/integration/PR-LEDGER.md (summary table first) and docs/integration/CONFLICTS.md on this branch.
  2. Decide the fix(ios): file intake ingredients by their actual storage rules #23 question (engine vs suite); implement on this branch, not on fix(ios): file intake ingredients by their actual storage rules #23's.
  3. After Sam's workflow approval, apply ci/pending-workflows/ios-ci.yml in a dedicated commit.
  4. Integrate backend trunk PR Backend trunk (do not merge as-is) #94 once its review completes.

Final head: d17f4cc5fca75e7528d7e57a9ba8c7d948006823


Join (2026-10-11): PR #45 and PR #44 folded

Handoff

Done: trunk carries both continuation streams (v20_historical_nutrition_snapshots + v21_bundle_ownership); every Linux check surface green on the combined head; ledger + CONFLICTS updated; pushed to this PR.
Left (owner decisions + known gaps): #71 (personal cookbook M1) and #83 (cooking checkpoints) remain unfolded pending their stacked-PR states (#71 iOS CI failing, M4/M5 open; #83's owner did not run its Linux tests); native/Xcode + macOS verification remains UNVERIFIED-NATIVE for the folded streams; this PR stays draft — the merge decision is Sam's.
Continue: branch fleet/fl-trunk-20261011; re-run any check package with Scripts/refresh.sh first, then swift test (fresh clones must stage — see CONFLICTS.md C5).

Proof - HealthProfile fail-closed allergen preferences (2026-10-12)

Fix for the review block "HealthProfile.swift:113 lets malformed or unknown allergen preferences fail open; fail closed." All commands run in the Obvious Linux sandbox (Swift 6.4) on a fresh clone of fleet/fl-trunk-20261011 at the audited head 4683161 (origin was still at 4683161 at push time; the push fast-forwarded 4683161..a6a02b5).

Semantics. AllergenExclusions.evaluateGroupSelection classifies the stored allergen_selected_groups JSON. Valid selections — every element a canonical group identifier after trim/lowercase, including the confirmed "no groups" [] — keep the exact previous filtering (pinned to the same exclusion-set numbers as before). Unreadable selections — malformed JSON, non-string elements, or any unknown group identifier (e.g. written by a newer app version) — fail closed: effectiveAllergenExclusionIds excludes the entire curated core universe (IDs 1-50) union the individual picks, the group picker prefills nothing, and allergenNeedsGroupConfirmation sends the profile back to reconfirmation. parsedAllergenSelectedGroups stays a rendering/prefill input only; enforcement no longer routes through it.

New tests: Tests/AllergenFailClosed/AllergenPreferenceFailClosedTests.swift (10 tests, run on Linux in the root package suite). The Xcode-only AllergenExclusionPolicyTests junk-pin test was updated to the fail-closed contract — macOS CI run pending on this head (native check not run from this sandbox). Known adjacent gap, out of this finding's scope: parsedAllergenIds (the separate allergen_ingredient_ids JSON) still parses malformed input to [] — flagged for the restriction-evaluation follow-up.

$ cd apps/ios && swift build && swift test        # Linux; baseline before the change: 16/16
warning: no rule to process file '/home/user/work/fl93-failclosed/FridgeLuck/apps/ios/App/AppAssets.xcassets' of type 'folder.assetcatalog' for architecture 'x86_64'
Build complete! (1.61 secs)
    /home/user/work/fl93-failclosed/FridgeLuck/apps/ios/Vendor/GRDB.swift/Support/GRDBTests.xcconfig
    /home/user/work/fl93-failclosed/FridgeLuck/apps/ios/Vendor/GRDB.swift/Support/Info.plist
Building for debugging...
[Computing dependencies]
[Provisioning 3 / 11]
warning: no rule to process file '/home/user/work/fl93-failclosed/FridgeLuck/apps/ios/App/AppAssets.xcassets' of type 'folder.assetcatalog' for architecture 'x86_64'
Build complete! (1.48 secs)
Test Suite 'All tests' started at 2026-10-12 00:16:34.762
Test Suite 'Debug-linux-x86_64.xctest' started at 2026-10-12 00:16:34.764
Test Suite 'AllergenPreferenceFailClosedTests' started at 2026-10-12 00:16:34.764
Test Case 'AllergenPreferenceFailClosedTests.testConfirmedNoGroupsSelectionStaysUnrestricted' started at 2026-10-12 00:16:34.764
Test Case 'AllergenPreferenceFailClosedTests.testConfirmedNoGroupsSelectionStaysUnrestricted' passed (0.007 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testEffectiveExclusionsUnionFailClosedGroupsAndIndividualIDs' started at 2026-10-12 00:16:34.770
Test Case 'AllergenPreferenceFailClosedTests.testEffectiveExclusionsUnionFailClosedGroupsAndIndividualIDs' passed (0.001 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testEmptyStringElementFailsClosed' started at 2026-10-12 00:16:34.771
Test Case 'AllergenPreferenceFailClosedTests.testEmptyStringElementFailsClosed' passed (0.1 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testEvaluateGroupSelectionClassifiesStoredJSON' started at 2026-10-12 00:16:34.872
Test Case 'AllergenPreferenceFailClosedTests.testEvaluateGroupSelectionClassifiesStoredJSON' passed (0.0 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testLegacyVersionStillNeedsConfirmationForValidSelections' started at 2026-10-12 00:16:34.872
Test Case 'AllergenPreferenceFailClosedTests.testLegacyVersionStillNeedsConfirmationForValidSelections' passed (0.1 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testMalformedSelectionJSONFailsClosed' started at 2026-10-12 00:16:34.972
Test Case 'AllergenPreferenceFailClosedTests.testMalformedSelectionJSONFailsClosed' passed (0.101 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testUnknownGroupIdentifierFailsClosed' started at 2026-10-12 00:16:35.073
Test Case 'AllergenPreferenceFailClosedTests.testUnknownGroupIdentifierFailsClosed' passed (0.0 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testUnknownIdentifierAmongValidOnesFailsClosed' started at 2026-10-12 00:16:35.073
Test Case 'AllergenPreferenceFailClosedTests.testUnknownIdentifierAmongValidOnesFailsClosed' passed (0.0 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testUnreadableSelectionExcludesExactlyTheCuratedUniverse' started at 2026-10-12 00:16:35.074
Test Case 'AllergenPreferenceFailClosedTests.testUnreadableSelectionExcludesExactlyTheCuratedUniverse' passed (0.0 seconds)
Test Case 'AllergenPreferenceFailClosedTests.testValidSelectionFiltersExactlyAsBefore' started at 2026-10-12 00:16:35.074
Test Case 'AllergenPreferenceFailClosedTests.testValidSelectionFiltersExactlyAsBefore' passed (0.0 seconds)
Test Suite 'AllergenPreferenceFailClosedTests' passed at 2026-10-12 00:16:35.074
	 Executed 10 tests, with 0 failures (0 unexpected) in 0.311 (0.311) seconds
Test Suite 'InventoryInvariantOperationTests' started at 2026-10-12 00:16:35.074
Test Case 'InventoryInvariantOperationTests.testAddLotClampsNegativeQuantityAndOutOfRangeConfidence' started at 2026-10-12 00:16:35.074
Test Case 'InventoryInvariantOperationTests.testAddLotClampsNegativeQuantityAndOutOfRangeConfidence' passed (0.059 seconds)
Test Case 'InventoryInvariantOperationTests.testConsumeExactBalanceReachesZeroWithoutNegative' started at 2026-10-12 00:16:35.133
Test Case 'InventoryInvariantOperationTests.testConsumeExactBalanceReachesZeroWithoutNegative' passed (0.066 seconds)
Test Case 'InventoryInvariantOperationTests.testConsumeMoreThanBalanceDepletesAndReportsShortfall' started at 2026-10-12 00:16:35.199
Test Case 'InventoryInvariantOperationTests.testConsumeMoreThanBalanceDepletesAndReportsShortfall' passed (0.049 seconds)
Test Case 'InventoryInvariantOperationTests.testConsumeWithNoStockTakesNothingAndWritesNoEvent' started at 2026-10-12 00:16:35.248
Test Case 'InventoryInvariantOperationTests.testConsumeWithNoStockTakesNothingAndWritesNoEvent' passed (0.05 seconds)
Test Case 'InventoryInvariantOperationTests.testConsumptionOrderIsExpiryFirstThenAcquiredThenId' started at 2026-10-12 00:16:35.299
Test Case 'InventoryInvariantOperationTests.testConsumptionOrderIsExpiryFirstThenAcquiredThenId' passed (0.044 seconds)
Test Case 'InventoryInvariantOperationTests.testExpiryTiesBreakByAcquiredThenId' started at 2026-10-12 00:16:35.342
Test Case 'InventoryInvariantOperationTests.testExpiryTiesBreakByAcquiredThenId' passed (0.027 seconds)
Test Case 'InventoryInvariantOperationTests.testFailedTransactionLeavesNoPartialConsumption' started at 2026-10-12 00:16:35.370
Test Case 'InventoryInvariantOperationTests.testFailedTransactionLeavesNoPartialConsumption' passed (0.026 seconds)
Test Case 'InventoryInvariantOperationTests.testOptionalRecipeIngredientsAreNotConsumed' started at 2026-10-12 00:16:35.396
Test Case 'InventoryInvariantOperationTests.testOptionalRecipeIngredientsAreNotConsumed' passed (0.026 seconds)
Test Case 'InventoryInvariantOperationTests.testPreviewMatchesActualDeductionWithServingsPortionAndSwaps' started at 2026-10-12 00:16:35.421
Test Case 'InventoryInvariantOperationTests.testPreviewMatchesActualDeductionWithServingsPortionAndSwaps' passed (0.031 seconds)
Test Case 'InventoryInvariantOperationTests.testRepeatedMealLogWithSameSourceRefDeductsTwice' started at 2026-10-12 00:16:35.452
Test Case 'InventoryInvariantOperationTests.testRepeatedMealLogWithSameSourceRefDeductsTwice' passed (0.028 seconds)
Test Case 'InventoryInvariantOperationTests.testRetireThenRestoreReconciles' started at 2026-10-12 00:16:35.481
Test Case 'InventoryInvariantOperationTests.testRetireThenRestoreReconciles' passed (0.028 seconds)
Test Case 'InventoryInvariantOperationTests.testSameSourceRefReingestIsSkipped' started at 2026-10-12 00:16:35.508
Test Case 'InventoryInvariantOperationTests.testSameSourceRefReingestIsSkipped' passed (0.036 seconds)
Test Case 'InventoryInvariantOperationTests.testSeededOperationSequencesHoldAllInvariants' started at 2026-10-12 00:16:35.544
Test Case 'InventoryInvariantOperationTests.testSeededOperationSequencesHoldAllInvariants' passed (11.115 seconds)
Test Case 'InventoryInvariantOperationTests.testUnknownRecipeIsANoOp' started at 2026-10-12 00:16:46.659
Test Case 'InventoryInvariantOperationTests.testUnknownRecipeIsANoOp' passed (0.059 seconds)
Test Case 'InventoryInvariantOperationTests.testZeroPortionMultiplierProducesZeroRequestedResults' started at 2026-10-12 00:16:46.718
Test Case 'InventoryInvariantOperationTests.testZeroPortionMultiplierProducesZeroRequestedResults' passed (0.061 seconds)
Test Case 'InventoryInvariantOperationTests.testZeroServingsIsANoOpAtRepositoryLevel' started at 2026-10-12 00:16:46.779
Test Case 'InventoryInvariantOperationTests.testZeroServingsIsANoOpAtRepositoryLevel' passed (0.041 seconds)
Test Suite 'InventoryInvariantOperationTests' passed at 2026-10-12 00:16:46.820
	 Executed 16 tests, with 0 failures (0 unexpected) in 11.745 (11.745) seconds
Test Suite 'Debug-linux-x86_64.xctest' passed at 2026-10-12 00:16:46.820
	 Executed 26 tests, with 0 failures (0 unexpected) in 12.056 (12.056) seconds
Test Suite 'All tests' passed at 2026-10-12 00:16:46.820
	 Executed 26 tests, with 0 failures (0 unexpected) in 12.056 (12.056) seconds
◇ Test run started.
↳ Testing Library Version: 6.4 (efd8d65801f569d)
↳ Target Platform: x86_64-unknown-linux-gnu
✔ Test run with 0 tests in 0 suites passed after 0.001 seconds.
$ git pull --no-rebase origin fleet/fl-trunk-20261011   # before push
Already up to date.
$ git log -1 --format="%h %an <%ae>"; git log -1 --format="%(trailers:key=Co-authored-by,valueonly)"
a6a02b5 Sam Gu <127461594+SamGu-NRX@users.noreply.github.com>
obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

$ git ls-remote origin refs/heads/fleet/fl-trunk-20261011   # after push
a6a02b54cb60ce07b3091ac08ccea657bae5f053	refs/heads/fleet/fl-trunk-20261011

Proof - strip newly added recipe instruction text (2026-10-12)

Reviewer finding addressed: LegacyBundles/v1_data.json:1536 newly ships unlicensed bundled recipe instructions (BBC-cited set). This fix removes every newly added recipe instruction text from the trunk while leaving the pre-existing alpha bundle byte-identical. Provenance: the recipe text entered via commit 42d9dc9 ("pin the public legacy bundle payloads for refresh adoption", folded through merge f192e67 for PR #44); the pin was a verbatim copy of alpha's apps/ios/Resources/data.json, so removing the pin's recipes array removes exactly the newly added text.

What changed

  • apps/ios/Resources/LegacyBundles/v1_data.json — the newly added 166-entry recipe list (with BBC-cited instruction text) is now "recipes": []. Ingredients (50), tags (12), and formatting are byte-identical to the pin commit's source payload; one diff hunk, 4986 lines removed.
  • apps/ios/Resources/LegacyBundles/manifest.json — only bundleId/dataSha256 recomputed for the redacted payload; catalog hash and row count unchanged.
  • scripts/data/export_legacy_bundle.py — new --omit-recipes flag so the committed pin stays reproducible from its source commit under the rights rule.
  • scripts/data/check_legacy_bundle_pins.py — a pinned payload carrying recipe entries now FAILS the corpus check (fail-closed rights rule, enforced for future pins).
  • scripts/data/tests/test_check_legacy_bundle_pins.py — new test_recipe_text_in_a_pin_fails pins the rights rule; committed-hash assertions updated; exporter reproducibility tests run with --omit-recipes.
  • apps/ios/Tests/LiveAssistantInstructionStepsTests.swift — the BBC-cited fixture (URL, step text, recipe title) replaced with synthetic content (example.org, "Fixture Noodle Bowl").
  • apps/ios/Feature/Assistant/LiveAssistantView.swift — one doc-comment domain example neutralized.
  • No test changes were needed in BundledDataRefreshTests.swift — the refresh suite's owned-or-explained contract absorbs the removal; legacy recipe rows adopt nothing and are recorded as unowned with diagnostics (fail-closed).
  • The pre-existing bundle apps/ios/Resources/data.json and the USDA catalog are untouched (not in this commit's diff).

Commands and output

$ git diff 2e779381..HEAD -- apps/ios/Resources/LegacyBundles/v1_data.json | grep -c '^+.*bbcgoodfood'
0
$ for f in $(git diff 2e779381 --name-only); do git diff 2e779381 -- "$f" | grep '^+' | grep -iE 'bbcgoodfood|sichuan' | sed "s|^|$f: |"; done   # residual scan over every trunk-diff file
(zero matches — scan complete)
$ git rev-parse 2e779381:apps/ios/Resources/data.json HEAD:apps/ios/Resources/data.json   # pre-existing alpha bundle is byte-identical
f2e23259013a7fac7ff1f57e77b71c30a4a0a47d
f2e23259013a7fac7ff1f57e77b71c30a4a0a47d
$ git diff 2e779381..HEAD -- apps/ios/Resources/data.json | wc -l
0

$ python3 scripts/data/check_legacy_bundle_pins.py --pins apps/ios/Resources/LegacyBundles
v1: data payload v1_data.json sha256=b9fb4b3c11b84a2684d382640c9b8b7445fb3cccc63c88a36c0a8a83f5eb42a6 ingredients=50 recipes=0 tags=12
v1: catalog export v1_usda_catalog_export.json sha256=7c2fd33e19be9d5c98a9614609a4705d1a143d945a5b0156f6bc43ffb9efdeda rows=800
OK: 1 pin(s) verified in apps/ios/Resources/LegacyBundles

$ python3 -m pytest scripts/data/tests/ -q
10 passed in 1.96s
# verbose run: 10 passed — includes new test_recipe_text_in_a_pin_fails (a recipe-bearing pin fails even with consistent manifest hashes)

$ cd apps/ios && swift test   # root app package suite, Linux
Test Suite 'All tests' passed
	 Executed 26 tests, with 0 failures (0 unexpected) in 22.429 (22.429) seconds
# baseline before this change: 16/16; 16→26 from the parallel allergen fail-closed fix (a6a02b5) joining the branch

$ cd apps/ios/Tools/bundled-refresh-check && bash Scripts/refresh.sh && bash Scripts/run.sh
Test Suite 'BundledDataRefreshTests' passed
	 Executed 23 tests, with 0 failures (0 unexpected) in 25.404 (25.404) seconds
EXIT=0
# with the stripped pin: "[AppDatabase] Bundle refresh: adopted 3 ingredients / 0 recipes" —
# ingredient adoption intact; legacy recipe rows stay unowned with diagnostics (fail-closed)

historical-nutrition-check was not run for this change: its fixtures are synthetic and it never consumes LegacyBundles/ — no input to it changed. Commits c304f50 (fix) and merge 5c52a70 are authored by Sam Gu 127461594+SamGu-NRX@users.noreply.github.com with the bot co-author trailer.

Handoff

Done: trunk diff vs alpha adds zero recipe instruction text; the v1 pin ships ingredient provenance only; pre-existing bundle untouched; all Linux surfaces I ran pass; rights rule now enforced by the pin checker. Left: none for this finding. Continue: fetch origin/fleet/fl-trunk-20261011, re-run the four commands above if touching the pin, exporter, or checker again.


🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

Proof - migration-order fix (2026-10-11)

Fixes the 63 native-CI assertion failures at 4683161 in production source only. No test expectation was modified, weakened, skipped, or xfailed. Native CI run for the new head 96c290d was queued at the time this section was posted; its conclusion is posted as a follow-up comment.

Root cause (migration order). The alpha + #45 + #44 composition registered v21_bundle_ownership unconditionally, so a database upgrading from the shipped v15 schema applied v16→v21 in one setup pass. MigrationUpgradeTests pins the upgrade tail as exactly v16→v20 (v21 must not join the setup stream), while the ownership-refresh stream requires the v21 schema to exist before it writes. Production now encodes that contract: Migrations.swift skips v21 registration when the latest applied migration is the shipped v15 schema, and a reusable DatabaseMigrations.applyBundleOwnershipSchemaIfNeeded(dbQueue) applies the extracted v21 schema from the ownership-refresh stream instead. AppDatabase.swift restores the zero-argument warmBundledContentIfNeeded() async throws launch form (the LaunchPerformanceRemediationTests source contract) while keeping a readiness: overload for BundledDataRefreshTests' .pending/.ready injections, and the refresh stream applies the deferred v21 schema before writing.

The 63 failures traced. 61× InventoryIntakeInferenceTests (intake inference: tip words now outrank names with earliest-word precedence, plural-aware token matching, pinned collision overrides), 1× LaunchPerformanceRemediationTests.testAppDatabaseSetupDefersBundledHydration (source contract on the warm signature, restored above), 1× MigrationUpgradeTests (upgrade-tail migration order, fixed above). All three areas are addressed by commits 3b7231e (migrations) and 644f3bc (intake); the evidence below is the Linux-reproducible surface at the pushed head.

$ cd apps/ios && swift test                                  # merged tree, head 96c290d
	 Executed 30 tests, with 0 failures (0 unexpected) in 24.012 (24.012) seconds
Test Suite 'All tests' passed
# 16 InventoryInvariants + 4 MigrationOrderReplayTests (new) + 10 AllergenFailClosed (parallel writer)

$ cd apps/ios/Tools/historical-nutrition-check && bash Scripts/refresh.sh && swift test
	 Executed 14 tests, with 0 failures (0 unexpected) in 1.558 (1.558) seconds
Test Suite 'All tests' passed
# baseline 14/14 ✅

$ cd apps/ios/Tools/bundled-refresh-check && bash Scripts/refresh.sh && Scripts/run.sh
	 Executed 23 tests, with 0 failures (0 unexpected) in 15.047 (15.047) seconds
Test Suite 'All tests' passed
# baseline 23/23 ✅ — this run compiles the real BundledDataRefreshTests.swift
# (its `warmBundledContentIfNeeded(readiness: .ready/.pending)` calls) against the
# restored overload pair, plus the production AppDatabase.swift, Migrations.swift,
# and InventoryIntakeService.swift on Linux

MigrationOrderReplayTests.swift (new, 4 cases) replays the migration-order contracts through the real migrator on Linux: fresh installs apply through v21; a v15 upgrade applies v16→v20 and defers v21 to the ownership-refresh stream; a v18 upgrade applies v19→v21; and the deferred ownership schema helper is idempotent. The parallel writer's commits (a6a02b5, c304f50) were merged with git pull --no-rebase (Package.swift conflict resolved to keep both test-source entries) and are included in the merged-tree runs above.

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

SamGu-NRX and others added 30 commits October 7, 2026 07:34
…Settings

Xcode 16's SDK doesn't mark UNNotificationSettings Sendable, so awaiting
notificationSettings() from the main actor failed the Swift 6 build in CI.
Read the settings through the callback API and return only the mapped
permission status.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The scheduler only needs identifiers, and Xcode 16's SDK doesn't mark
UNNotificationRequest Sendable, so returning requests to the scheduler
actor failed the Swift 6 build in CI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Move the dismissal/step-navigation decisions of SpotlightTutorialOverlay
into a pure SpotlightOverlayStateTransitions policy that the view applies
verbatim, so the Skip-versus-navigation ordering is unit-testable without
SwiftUI. No behavior change: the policy encodes today's semantics, and the
new tests pin ordinary navigation, the skip-then-delay unmount, and the
dismissDelayElapsed guard. The Skip-then-Next/Back race tests are expected
to fail against this commit — they document the defect, the fix follows.
…ftPM dep cache

The advisory previously ran on every pull_request event, consuming a
second macos-26 job per PR. It now runs only on pushes to main,
workflow_dispatch, and pull requests carrying the 'periphery' label
('labeled' added to pull_request.types so applying the label requests
the scan; the condition checks the full label list so an already
labelled PR is scanned on open and on new pushes). build-and-test
skips label-change events so its effective triggers are unchanged.

Label-change events get their own concurrency group suffix so adding a
label can never cancel an in-flight build-and-test run; all other
events keep the original group and cancel-in-progress behavior.

Adds an actions/cache step for ~/Library/Caches/org.swift.swiftpm,
keyed on the committed apps/ios/Package.resolved and project.yml plus
the pinned Xcode version, so dependency changes invalidate the cache
and caches never cross incompatible Xcode versions. Derived build
artifacts are not cached.
… core ingredients

The helper tests pin the intended matching semantics first (failing-first):
whole-word plural-aware name matching, tip-first-over-name precedence with
first-storage-word resolution, name fallback when the tip is missing or
unrecognized, and the 50 bundled core ingredients under their bundled tips
and under name-only fallback. Ambiguous storages (tomato, banana, avocado,
lemon, lime, apple, corn) are pinned with notes instead of being presented
as universal guidance.
Le Chef's instructionSteps split the raw recipe instructions by line, so the
bundled "Source: https://…" line rendered as step 1 and the recipe's own
"1." numbering printed under the panel's own counters.

Parse once at init with CookingGuideSteps — the same parser the offline
cooking guide uses — expose the attribution outside the step count, and show
it once on the final step like the guide. An empty or attribution-only
recipe now yields zero steps with no false final-step state.

GeminiLiveSessionClient sent the raw instructions string to the backend;
it now sends the cleaned steps (still a single string, matching the
backend's StoredRecipeContext) plus a separate sourceAttribution field
inside selectedRecipe. No backend changes.
Inside the 240 ms dismiss delay, a Next or Back tap cancelled the scheduled
unmount without restoring appeared, leaving SpotlightTutorialOverlay mounted
at opacity 0 with its full-screen dimming layer blocking touches — the app
read as frozen. Navigation no longer cancels the pending unmount: once
dismissal begins it always completes.

The one-line change sits in SpotlightOverlayStateTransitions; the view is
untouched. CI on 23f6008 reproduced the defect (both race tests failed with
appeared: false, isPresented: true) while the behavior-preservation tests
passed; this commit flips the suite green.
Drives the real InventoryRepository through SplitMix64-seeded operation
sequences (8 seeds x 48 ops) on migrated in-memory GRDB databases and
checks repository-wide invariants after every operation:

- remaining_grams reconciles with the audit-event ledger per lot
- balances never go negative
- consumption follows expiry-first, then acquired_at, then id, with
  expiry-less lots last
- grocery intake retries dedupe by source_ref; meal logging has no
  dedup and deducts on every log
- shortfall/requested/consumed identities, including servings, portion
  multiplier, and swap math
- scan-review retire/restore reconcile through adjust events
- previewConsumption matches applyConsumption per row for logs whose
  requests are unique per ingredient (documented divergence for logs
  that request one ingredient twice over budget)

Failures print the seed and a reduced replayable operation sequence.

To build the repository target on Linux without touching the Xcode
project, the SwiftPM test target now scopes the app sources to
FLInventoryCore (GRDB inventory stack) with an InventoryCoreShim for
the repository notification name, and LiveAssistantPanelLayout guards
its CoreGraphics import with a Linux CGFloat fallback.
Group toggles previously persisted their keyword-matched ingredient IDs,
which made saved IDs double as group intent. That broke in practice: the
Gluten keywords match none of the 50 bundled core ingredients, so its
onboarding toggle was disabled, and RecipeRepository could never honor a
group the user wanted.

Group intent now has its own record:

- AllergenExclusions (new, Foundation-only) holds the canonical group IDs,
  a curated FDA/EU-cited membership table for all 50 bundled core
  ingredients, and the effective-exclusion computation (selected groups
  UNION individual exclusions). RecipeRepository consumes it instead of
  re-deriving exclusions from saved IDs.
- health_profile gains allergen_selected_groups and
  allergen_preferences_version (v19, append-only). Deliberately no
  backfill: legacy IDs cannot show which groups the user chose, so legacy
  rows keep version 0, keep their ingredient exclusions, and are asked to
  reconfirm instead of having exclusions presented as complete.
- Onboarding requires an explicit choice per profile: at least one group
  selected, or a dedicated 'no allergen groups' confirmation. Silence is
  never read as 'no allergies'. Selection is the group identifier itself,
  never derived from keyword-matched IDs.
- Settings food preferences gains the same explicit group chips,
  'no groups' confirmation, and save gate; Profile shows group state and
  a reconfirmation notice for unreviewed profiles.
- AllergenExclusionPolicyTests covers the confirmation gate, group
  normalization, effective exclusions (soy sauce is Soy+Gluten; peanut
  butter is Peanut-only; oats under Gluten), the 50-ingredient table
  integrity, and the v19 upgrade defaults.

All changed files pass swiftc -parse; the Foundation-only contract
typechecks standalone. Full build and tests run in CI (macOS).
…to FLFeatureLogic

ReverseScanService now computes the mean detection confidence, the four
confidence signals, and the hard-failure reasons through the new public,
pure ReverseScanFeatureLogic in the FLFeatureLogic target, replacing the
three private calculation helpers. The shipped rules are unchanged:
Float32 confidences widen to Double before the mean; the margin is 0.82
for a single candidate and clamped 0.5 + gap otherwise; ambiguity is a
strict gap < 0.06; hard-failure precedence stays no-candidates >
missing required > ambiguity. The service distills candidates to
ReverseScanCandidateFacts before calling the calculation.

Add ReverseScanFeatureLogicTests, which recompute all 16 committed
reverse-meal-v1 producer-state projections against
projected-requests.jsonl (|delta| <= 1e-12, exact strings) and pin the
shipped rules independently. Verified on Linux Swift 6.4: 11/11 tests
pass and swift-format lint --strict is clean. The full FLFeatureLogic
target does not build on Linux because of a pre-existing CoreGraphics
import in LiveAssistantPanelLayout.swift; app builds and tests run on
macos-26 CI.
Move fixture, snapshot/ledger sweep, seeded generator, and fuzz driver
into Tests/InventoryInvariantSupport.swift (internal static API on
InventoryInvariantSupport) so additional invariant suites can reuse the
same migrated fixture and checks without duplication. The SwiftPM test
target now sources the Tests/InventoryInvariants directory, so new
suites added there are picked up without Package.swift churn.

The seeded fuzz test now loops 24 seeds x 64 operations (previously 8 x
48) through InventoryInvariantSupport.runSeededFuzz, the extracted
generate/run/reduce driver. Full suite: 16/16 pass in ~8.6s.
…ower icon

The Progress and Dashboard buttons said 'Reverse Scan a Meal' with the
camera.macro symbol (which testers read as a tulip), while the scan-mode
fan menu and the flow's own navigation title already call the same flow
'Log a Meal'. Unify on that name with the flow's fork.knife food symbol,
add explicit accessibility labels and hints at both buttons, reword the
one user-facing error string that mentioned reverse scan, and add a
regression test pinning the plain name and food symbol.
…ure function

clearSelection(for:) was instance-bound SwiftUI state, untestable. Extract
IngredientReviewView.notThisItemOutcome as a pure decision and apply its
outcome in clearSelection. Behavior-preserving: didRejectDetection is false,
so the new rejection wiring is inert; the added tests encode the contract we
want (a tap must never be a silent no-op) and fail on this commit by design.
VoiceOver heard a scan's outcome but nothing when it began, and the
review's photo-strip thumbnails were invisible to it. A scan now
announces what it is reading (and anchors focus on the heading, since
the placeholder replaces whatever was under focus), and each thumbnail
carries a label naming its place and slot. Both texts are pure
functions on OnboardingKitchenReview, with tests.
…ty sizes

The review's 'Continue Without Adding' truncated to
'Continue Without Add…' at accessibility text sizes. Buttons grow
vertically instead.
Pins the exact VoiceOver wording of OnboardingKitchenReview's
announcement(for:selectedCount:) and unreadNotice(for:place:) on
their edge cases: nothing captured, both sections partly unread
(fridge notice before pantry), the singular/plural noun, a zero
selected count, and which unread warnings fire versus stay silent.

Co-authored-by: Sam Gu <sgu07966@gmail.com>
…rget

On a first run the learning cache is empty, so no candidate chip is
pre-selected and clearSelection mutated nothing at all — the tap reported
dead in the first-run walkthrough. It did not miss: in that state the action
was a guaranteed silent no-op.

- notThisItemOutcome now always rejects the row, so every tap produces a
  visible state change (the row leaves the review list; counts, pill and
  progress bar follow via reviewableNeedsConfirmation). A never-selected
  row was never ingested (ingestConfirmedScan skips unconfirmed detections),
  so this is review-screen state only.
- 'Not this item' and 'Choose another' had ~17pt tall hit targets (caption
  label, no frame expansion); both now get a 44pt minimum frame with a
  contentShape, per the HIG minimum.
- Adds accessibilityIdentifier ingredient_review_not_this_item.
…l buttons

The a11y audit showed the 'Double tap to ...' phrasing from ScanModeMenu
is the codebase's lone exception; house style elsewhere (e.g.
KitchenSections) is a capitalized outcome sentence with a period.
…ng scan

The independent VoiceOver audit confirmed the scan-start gap is fixed
and found no blockers; it flagged small double-speech and
navigation-noise issues, fixed here: the detection row no longer says
'selected' twice (the trait speaks it), the selection count and the
capture step's photo count are polite live regions, each section header
reads with its item count as one focus stop, the capture steps get
header traits, labeled thumbnails, a hidden hero icon, and the shared
buttons hide their decorative symbols.
Merged from obv/fridgeluck-056--tests-edge: both sections partly
unread, singular and plural, a zero selected count, and the unread
notice's nil cases.
…duce Motion

The Reduce Motion audit found zero violations but flagged two sites
that are RM-safe only because their callers happen to pass gated
withAnimation: the selection count's digit roll and the capture strip's
scale-in transition. Both now gate themselves.
Existing installs hydrated recipes and ingredients from the data.json and
USDA catalog payloads that are public in git history. Pinning those exact
payloads (with sha256 manifest) lets a future bundle refresh adopt legacy
rows by complete unique match instead of guessing from titles, and refuse
to run when the pins are missing or corrupt.

The v1 pins are generated from origin/main's data.json (62d9ed0a...) and
the committed USDA catalog sqlite via scripts/data/export_legacy_bundle.py,
which stays the reproducible way to add a pin for every future bundle
content change.
…ication

Ownership keys give bundle-written rows a stable namespaced identity
(fridgeluck.bundle.recipe/<id>, fridgeluck.bundle.ingredient/<id>,
usda.fdc/<fdcId>) that survives bundle content changes and installed-id
drift. Canonical hashes cover exactly the columns each writer produces
for a row's provenance, excluding ids, timestamps, and user-controlled
state, so a refresh can tell untouched rows from edited ones. The pin
loader verifies every pinned prior payload against its manifest sha256
and refuses loudly on missing, corrupt, or tampered pins.
V20 adds ownership_key and bundle_content_hash to recipes and ingredients
(partial unique indexes: one row per bundle entry), plus a
bundle_refresh_diagnostics table keyed by entity/ref/code for upserts.

The refresher runs in one write transaction: validate payload, adopt
legacy rows against pinned snapshots (content + relationship fingerprints,
ambiguous or modified rows stay unowned with diagnostics), apply the
current payload (update bundle-owned rows in place, insert new entries,
refuse to steal unadopted rows or overwrite modified ones), record
removed-from-bundle keys, and verify postconditions — every current
payload entry must end owned-and-current unless a diagnostic explains
why, else the transaction rolls back. Rows are never deleted.
…fresh

First-launch data.json inserts and recipe hydration now write ownership
keys and content hashes alongside the rows, the USDA import stamps FDC
provenance only on rows it actually inserted, and demo repair is
constrained to unowned bundled rows so user-created recipes keep their
identity. AppDatabase runs the atomic ownership refresh after hydration,
non-fatally, retrying on the next launch. LegacyBundles ships as a
folder reference.
Covers the V20 migration shape and partial-uniqueness semantics, the
row-side vs payload-side hash round trip, adoption on legacy-shaped
databases (id preservation, ambiguity, unmatched rows), payload
application (inserts, updates, user-edit protection, name twins and
name conflicts), removal diagnostics, idempotence, mid-pass rollback,
pass construction, and the full launch path against the real bundle
resources.
… hash projections

The refresh engine now takes optional GRDB database-value dictionaries,
selects ingredient projections by ownership namespace instead of trial
and error, guards missing rows rather than force-unwrapping, and
simplifies removal and postcondition logic. Owned-but-modified rows are
accepted by postconditions when a modified_row diagnostic explains why
their content was preserved. Row-side projections share field names,
order, and encodings with the payload-side functions so a row hash can
be compared against a payload hash.
check_legacy_bundle_pins.py re-derives every manifest claim from disk:
payload hashes, bundleId derivation, catalog shape, and row counts,
failing nonzero with the exact hashes on any drift. The pytest controls
exercise the real CLI against corpus copies for corruption, missing
files, missing/corrupt manifests, and row-count drift. The exporter
gains an --out-dir flag for non-destructive test runs, merges pins by
slug instead of overwriting the manifest, and a test proves it
reproduces the committed v1 corpus byte for byte.
…riter

Freeze the seven macro-nutrients, effective grams, substitute identity,
swap ratio, line order, recipe servings, version, and provenance for every
logged meal, so later catalog edits cannot rewrite history.

- v20 migration creates snapshot headers and per-required-ingredient lines
  and backfills existing cooking history from the catalog as of upgrade
  (provenance 'upgrade_backfill'); empty recipes complete with zero-line
  snapshots and orphaned histories are skipped
- NutritionSnapshotService captures snapshots inside the logging
  transaction and folds journal totals from stored lines with the same
  grouping the old catalog join used
- unswapped lines freeze ratio 1.0 (the LEFT JOIN can yield NULL); missing
  or newer-version snapshots throw instead of falling back to the mutable
  catalog
SamGu-NRX and others added 17 commits October 11, 2026 13:07
…w delta held)

Folds obv/fridgeluck-054 @ 51b449a into the app integration trunk, fold 11 of 12. The workflow change arrives in this merge and is restored to the alpha state in the follow-up commit; the proposed delta is staged under ci/pending-workflows/ for Sam to approve.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Restores .github/workflows/ to the alpha state (2e77938) after the #18 merge and stages the proposed iOS CI change under ci/pending-workflows/ios-ci.yml with its purpose, trigger and provenance in the header. Only Sam can approve workflow edits.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Folds obv/fridgeluck-033 @ df1fb29 into the app integration trunk, fold 12 of 12. Conditional fold: taken because it merges cleanly. Extends the apps/ios package with the invariant suite targets.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…eview

Alpha's trunk (601d827, f7e5a7b) replaced previewConsumption(ingredientGrams:)
with the recipe- and servings-driven preview that depletes the same requests
as applyConsumption, claiming across repeated rows. #31's suite was written
against the pre-601d827 API and did not compile against the trunk.

Keeps the trunk's product side (CI-green composition on alpha; every other
consumer uses it; the old quantity-list API has no remaining production
caller) and re-points the suite: the seeded preview test drives the same
recipe/servings/portion/swaps inputs as the apply and asserts the claimed
balance falls 100 to 16 across repeated requests; the property helper
compares preview rows consumably and treats a missing row as the
nothing-in-stock case. Both sides are recorded in
docs/integration/CONFLICTS.md.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…FLICTS.md

One row for every open PR (83 at the roster snapshot + this trunk PR):
folded 19, needs-owner 29, excluded-research 21, excluded-backend 11,
pending owner Handoff 2, superseded-by #93 1, trunk row 1, excluded-broken 0.
Records the migration order (v1-v18 unchanged + v19_explicit_allergen_groups
from #32), the backend trunk's no-open-PR-yet state, and the brief's #34/#35
fold-entry mismatch (branch+SHA identified PR #35; see CONFLICTS.md C3).

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…losures

DatabaseMigrations.migrate takes a DatabaseQueue; #32's own v19 test passed
the transaction-scoped Database from inside db.write, which does not compile
on any base (source-red: the PR's own macOS build-and-test failed at
c18ece1 for this error, and the two broken lines come from #32's diff —
the test file did not exist on its base). Hoisting both migrate calls to the
queue keeps the test's intent exactly: migrate the empty queue to
v18_cooking_history_swaps, seed the legacy row, migrate to head, assert the
safe defaults. Merge-broken-on-trunk things get fixed; the source-red
attribution is recorded in docs/integration/PR-LEDGER.md row #32.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
… ledger

#32's own build-and-test failed at its head c18ece1; the two broken lines
come from its diff and never compiled anywhere. The trunk fix is 56b212f.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…never compiled

Source-red: PR #37's own macOS build-and-test failed at its head 4f51d21
with the identical error set (OnboardingInventorySteps.swift:446
accessibilityLiveRegion has no member + .polite base inference, and
OnboardingKitchenSupport.swift:181 type-check timeout); alpha at 2e77938 is
green, so the merge introduced nothing new. Kept every audit finding and
restructured only what the compiler could not check: the liveRegion now sits
on the same Text-chain shape as the line that already compiles at
OnboardingKitchenSupport.swift:211, the ContentTransition ternary is
explicitly typed, and the thumbnail accessibilityLabel interpolation is a
standalone let as the diagnostic suggests. Also refreshes the ledger: the
backend trunk landed as draft PR #94 (head 18fb5f9), so backend rows and
the summary point there (85 rows, trunk rows 2).

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Root cause confirmed: SwiftUI has no accessibilityLiveRegion modifier
(anywhere, not just this SDK) - the run on a5ac7c7 now reports it plainly
on both counters (445 InventorySteps, 212 KitchenSupport) once the
type-check budget was freed. It is an Android API concept; the iOS
equivalent for a counter that changes as the user works is
accessibilityAddTraits(.updatesFrequently), which keeps PR #37's VoiceOver
intent that the N-of-M counters stay live. Both counters now carry it.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
… with frozen-bit proof

Exercise the real PR #44 refresh owner (vendored read-only at head
7dc2b21) against the cutover world of this branch:

- E1: the .pending readiness default refuses before any write - fail-closed
  holds at the integration seam, not only in unit tests.
- E2: adoption stamps ownership without disturbing a single frozen bit; a
  changed payload (ingredient 1 calories 52.17 -> 55.55) updates the owned
  row in place while every frozen output stays Double.bitPattern-identical:
  per-meal journal macros, today, daily totals, and captured Health macros
  for all meals. A non-vacuousness check verifies the original live-table
  aggregation's bits DID move.
- E3: deleting one snapshot header flips the same refresh to refusal with
  nothing further written.

VendoredBundleRefresh/ carries the refresh-owner sources as test-only
fixtures (byte-identical after documented import shims; see PROVENANCE.md).
No production sources changed; public interfaces unchanged.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
… the three source-red attributions

PR #32's HealthProfile now persists allergen_selected_groups and
allergen_preferences_version, but the old OnboardingGatePolicyTests
fixture hand-rolled the pre-v19 column set, so both of its tests failed
with 'no column named allergen_selected_groups'. The fixture now mirrors
the v19_explicit_allergen_groups definitions exactly (TEXT NOT NULL
DEFAULT '[]' / INTEGER NOT NULL DEFAULT 0). Also regenerates the ledger:
#23 is recorded source-red with its own failing run (engine identical on
both sides - the merge added only the suite), #37's compile fix commits
are named, #32's fixture note is added, and the backend trunk is now
draft PR #94 (second trunk row, 85 rows total).

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
PR #32 appended v19_explicit_allergen_groups but never updated
MigrationUpgradeTests' suffix window (its own CI failed at compile, so the
suite never ran there). The upgrade-from-v15 test now expects the last
four applied migrations: v16, v17, v18, v19.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…eck package

The bundled-refresh tests only ran from a temporary harness; a reviewer
could not re-run them. apps/ios/Tools/bundled-refresh-check copies the
full Linux-compilable persistence surface (25 persistence + 12 model
files) and the real BundledDataRefreshTests.swift, installs the shipped
bundle fixtures where the Linux test runner's Bundle.main resolves them,
and runs the suite: 23 tests, 0 failures at this commit (Swift 6.4,
GRDB 7.x). Two new tests pin the bundle input contract's behavior:
user data referencing a removed recipe stays readable (rows are never
deleted), and reusing a bundle id rewrites the owned row in place.
BUNDLE-INPUT-CONTRACT.md documents the schema, ID stability rules, and
removed-recipe behavior a new corpus must satisfy.

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>

# Conflicts:
#	apps/ios/Tests/MigrationUpgradeTests.swift
#	apps/ios/Tests/OnboardingGatePolicyTests.swift
…m PR #44 (c316f5f)

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…sh fresh-clone staging

Flow E hand-applied the vendored v21_bundle_ownership after the app
migrator (the #44 merge now registers it) — duplicate column error. The
vendored helper is retired per its own PROVENANCE protocol; the fixture
relies on the real migration sequence (verified byte-identical across
7dc2b21..c316f5f for Migrations.swift). bundled-refresh-check
Scripts/refresh.sh now mkdir -p's the Fixtures directory (git does not
track the empty, fully-ignored directory).

Evidence on the combined trunk head (Linux, Swift 6.4, GRDB 7.11.1):
historical-nutrition-check 14/14, bundled-refresh-check 23/23,
scripts/data pytest 9/9, legacy bundle pin checker OK.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…CTS C4/C5

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

@SamGu-NRX SamGu-NRX left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: BLOCK

Reviewed 4683161801a9a3fd6be705eb152ba724ec6163cd against fl-integration-f1 and PR #86's binding safety/rights contracts.

  • apps/ios/Domain/Models/HealthProfile.swift:113-120 turns malformed allergen-group JSON into an empty set and silently removes unknown group IDs. AllergenGroupMembership.swift:251-258 discards those unknown values; RecipeRepository.swift:126-149 then proceeds with the reduced exclusions. A profile with version 1 and malformed stored groups can receive milk recipes without a failed-check state or reconfirmation gate. Preserve uncertainty and refuse restricted recommendations until the preferences can be checked. A warning based only on version does not cover parse failures.
  • apps/ios/Resources/LegacyBundles/v1_data.json:1536 contains BBC Good Food instruction text. This new bundle has 166 recipe records, 141 with BBC URLs, and no per-use licence evidence. project.yml:75-76 ships the whole folder. A historical pin does not grant redistribution rights. Remove unlicensed instruction text from shipped pins or supply the required rights evidence; preserve only the historical facts needed for nutrition where permitted.
  • apps/ios/Tests/MigrationUpgradeTests.swift:18 is red at this exact head: its expected suffix ends at v20 while the actual sequence ends at v21. The same run also fails LaunchPerformanceRemediationTests.swift:50 and the intake-inference assertions. Run 38156342266 reports 421 tests and 63 failures. Earlier-head claims of only intake failures cannot establish acceptance of this joined head. Resolve and record the current failures before treating this trunk as verified.

Criteria: fold ledger partly met; dependency-ordered join partly met; migration acceptance missing; current combined native tests missing; workflow installation boundary met; safety/rights acceptance missing. Fully met 1/6. Linux package results in the Handoff do not cover the failing app-target checks.

This blocking review examined the production allergen path, shipped legacy inputs, project resource wiring and exact-head CI. The remaining integration/UI and refresh paths need further review; no exhaustive safety clearance is claimed. No local tests, Xcode or simulator were run.

@SamGu-NRX

Copy link
Copy Markdown
Owner Author

FLIngredientFacts integration fix list — restriction checks fail open

Audited at PR head 4683161801a9a3fd6be705eb152ba724ec6163cd. This fix list is committed under docs/audits/ingredient-facts/PR-93.md on branch overhaul/ingredient-facts.


PR #93 — Integration fix list: restriction checks fail open

PR head at audit: 4683161801a9a3fd6be705eb152ba724ec6163cd (branch fleet/fl-trunk-20261011).
Canonical facts source to adopt: packages/FLIngredientFacts + data/ingredient-facts on overhaul/ingredient-facts.

1. Where the restriction code sits at this head

File Symbols Role
apps/ios/Domain/Allergens/AllergenGroupMembership.swift AllergenGroupID, AllergenExclusions.coreMemberships (line 85), memberships(forCoreIngredientID:) (line 213), coreIngredientIDs(excludedByGroups:) (line 232), effectiveExcludedIngredientIDs(selectedGroups:individualExclusions:) (line 241) Curated membership table for the 50 bundled core ingredients; computes a profile's effective exclusion ID set
apps/ios/Domain/Models/HealthProfile.swift effectiveAllergenExclusionIds (line 134), dietaryExcludedIngredientIds (line 173), requiredRecipeTagMask (line 159) Diet → ingredient-ID list mapping; allergen groups → excluded IDs
apps/ios/Platform/Persistence/Repository/RecipeRepository.swift recipe filter at lines 122–127 Consumes the union of effectiveAllergenExclusionIds and dietaryExcludedIngredientIds; recipes pass when no ingredient ID is in that set
apps/ios/Tests/AllergenExclusionPolicyTests.swift policy tests Locks in the current (fail-open) table semantics

2. Fail-open behavior found

Every path here is an allow-by-default keyed on a local 50-ingredient list; anything the list doesn't name is treated as safe.

  1. Unknown IDs resolve to "no groups" — memberships(forCoreIngredientID:) line 215: coreMembershipByID[ingredientID]?.groups ?? []. The doc comment says it plainly: unknown IDs "return an empty set: the curated table makes no claims about them." USDA-catalog ingredients (800 rows) and any recipe ingredient outside the bundled 50 are therefore never excluded. A wheat avoider is offered tamari; a milk avoider is offered cream cheese; neither appears in the 50-core table, so neither is ever blocked.
  2. Diet exclusions are a hardcoded 5-item list — HealthProfile.dietaryExcludedIngredientIds lines 173–182: if diet == "vegan" { excluded.formUnion([12, 13, 14, 32, 50]) } (cheese, milk, butter, yogurt, sour cream). Egg (ID 1), honey, ground beef (ID 31), chicken breast (ID 33), salmon (ID 29) and canned tuna (ID 37) are not in the vegan list, so a vegan profile is offered recipes containing eggs and meat. vegetarian, pescatarian and keto return an empty set — the ingredient-level diet check is a no-op for them (a vegetarian is offered lard or Worcestershire sauce in any recipe; neither check fires).
  3. The recipe filter can only see the list — RecipeRepository lines 122–127 filter recipes by ID membership in the exclusion set. No per-ingredient property lookup exists: absence of an exclusion entry means "allowed". Compound ingredients are not decomposed (soy sauce is special-cased as soy+gluten in the table, but tamari, hoisin, teriyaki, worcestershire are not resolvable at all).
  4. Duplicate divergent lists — allergen groups live in AllergenGroupMembership.coreMemberships, diet exclusions in HealthProfile, and recipe-tag gating in requiredRecipeTagMask (which, e.g., does nothing for pescatarian). Three vocabularies, no shared source of truth; the gluten-free diet does not exist as a selectable concept outside AllergenGroupID.gluten (wheat_gluten), and "gluten-free" is asserted per-ingredient in the table without product evidence (oats are blanket .gluten, generic tamari is absent entirely).

3. Exact changes needed (file by file)

Goal: replace the per-module lists with fail-closed evaluation from FLIngredientFacts; treat unknown as ask the user, never allowed; meal restrictions are the union of every eater's restrictions (FLRestrictions.union).

3.1 apps/ios/Platform/Persistence/Bundle/BundledDataLoader.swift (+ Package.swift / project target)

Add the facts store to app dependencies so every consumer shares one instance:

import FLIngredientFacts

public enum AppFacts {
  /// One store for the process, pointed at the bundled data/ingredient-facts copy.
  public static let store: FLIngredientFactsStore = try FLIngredientFactsStore(
    dataDirectory: Bundle.main.resourceURL!.appendingPathComponent("data/ingredient-facts", isDirectory: true))
}

Ship data/ingredient-facts/identities.json and facts.json as bundle resources (same path as data.json). FLIngredientFactsStore.init(dataDirectory:) throws if the dataset is missing — do not fall back to the old lists on error; surface the error.

3.2 apps/ios/Domain/Models/HealthProfile.swift

Replace dietaryExcludedIngredientIds's hardcoded ID list with FLRestrictions construction so the profile speaks the shared vocabulary:

// before
var dietaryExcludedIngredientIds: Set<Int64> {
  var excluded: Set<Int64> = []
  if diet == "vegan" { excluded.formUnion([12, 13, 14, 32, 50]) }
  return excluded
}

// after
var restrictions: FLRestrictions {
  var r = FLRestrictions()
  for group in parsedAllergenSelectedGroups { /* map each AllergenGroupID to FLAllergen */ r.allergens.insert(mapped) }
  switch selectedDietID {
  case "vegan": r.diets.insert(.vegan)
  case "vegetarian": r.diets.insert(.vegetarian)
  case "pescatarian": r.diets.insert(.pescatarian)
  case "glutenFree": r.diets.insert(.glutenFree)
  default: break
  }
  return r
}

Delete dietaryExcludedIngredientIds once no caller depends on it (see 3.3). Keep needsGroupConfirmation — the unknown-preferences-version flow maps to "ask the user" and stays.

3.3 apps/ios/Platform/Persistence/Repository/RecipeRepository.swift

Replace ID-membership filtering with per-ingredient evaluation against the union of the household's eaters' restrictions:

// before (lines 122–127)
let excludedIngredientIds = profile.effectiveAllergenExclusionIds.union(
  profile.dietaryExcludedIngredientIds)
...
if excludedIngredientIds.contains(ingredientID) { continue }

// after
let meal = FLRestrictions.union(profiles.map(\.restrictions))   // every eater
for ingredient in recipe.ingredients {
  switch AppFacts.store.evaluate(ingredient: ingredient.name, restrictions: meal) {
  case .violated: continue                        // hard filter
  case .unknown(let u): continue                  // fail closed: ask, don't serve
  case .allowed: break
  }
}

unknown candidates should be surfaced in the UI as "confirm with the user" (the existing review affordances in Feature/Ingredients/IngredientReviewView.swift), never silently ranked.

3.4 apps/ios/Domain/Allergens/AllergenGroupMembership.swift

Keep AllergenGroupID and the versioned-preferences flow, but retire the hand-maintained coreMemberships table as an enforcement source. memberships(forCoreIngredientID:) and effectiveExcludedIngredientIDs become legacy shims that delegate to AppFacts.store.evaluate (or are deleted with their call sites). The table's evidence strings (FDA/EU bases) migrate into data/ingredient-facts/facts.json evidence entries rather than living in Swift.

3.5 apps/ios/FeatureLogic/Recipe/ReverseScanFeatureLogic.swift and swap/union call sites

Every "can I eat this?" verdict and every ingredient swap must run store.evaluate(ingredient:restrictions:) with the union of all eaters' restrictions; a swap whose substitute evaluates unknown asks the user. The comment in AllergenGroupMembership.swift ("Recipe generation and ingredient swaps should call the same functions") becomes literally true by pointing at the store.

3.6 Tests

Update apps/ios/Tests/AllergenExclusionPolicyTests.swift: the policy tests should assert fail-closed behavior (an ingredient absent from the dataset ⇒ not served), the reviewer examples (tamari blocked for wheat+soy; cream cheese blocked for milk; lard and Worcestershire blocked for vegetarian; generic tamari NOT gluten-free without a label-claim evidence entry), and FLRestrictions.union meal semantics.

Conformance kit (Swift)

cd packages/FLIngredientFacts && swift build && swift test

All conformance cases must pass through the real store before this PR's restriction behavior is considered compliant.

SamGu-NRX and others added 3 commits October 12, 2026 00:14
HealthProfile.parsedAllergenSelectedGroups dropped unparseable JSON and
unknown group identifiers on the floor, so a corrupted or newer-version
`allergen_selected_groups` payload evaluated to "no groups" and the
recipe filter served allergens. Stored selections are now classified
(AllergenExclusions.evaluateGroupSelection): valid selections keep the
exact previous filtering, while unreadable ones fail closed — the full
curated core universe is excluded, the picker prefill shows nothing, and
the profile is asked to reconfirm.

The policy files (HealthProfile, Recipe, AllergenGroupMembership) join
the FLInventoryCore Linux slice so the contract is testable under
`swift test`: 10 new tests cover valid unchanged filtering, malformed
JSON, and unknown identifiers; suite 26/26 green (baseline 16/16 intact).

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…le pin

The v1 pin introduced for refresh adoption carried the full 166-recipe
list, newly shipping BBC-cited instruction text on the trunk. The pin now
ships the ingredient provenance only (recipes: []) — the pre-existing
alpha data.json is untouched, and legacy recipe rows adopt nothing: the
refresh records them as unowned with diagnostics (fail-closed).

- export_legacy_bundle.py: add --omit-recipes so the committed pin stays
  reproducible from its source commit under the rights rule
- check_legacy_bundle_pins.py: a pinned payload carrying recipe entries
  now fails the corpus check
- scripts/data tests: pin the rights rule and the regenerated hashes
- LiveAssistant tests and LiveAssistantView doc comment: replace the
  BBC-cited fixture text with synthetic content

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>

@SamGu-NRX SamGu-NRX left a comment •

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: BLOCK

Reviewed 5c52a709e1734e7450a62371c4b9bcf622c18ad0 against app-trunk part 1 of fl-integration-f1 and SPEC 3.1/3.3. The newly shipped legacy pin now has zero recipes, its manifest hash matches, and the rights regression test rejects recipe-bearing pins even with consistent hashes. Malformed group selections now exclude the 50 core IDs. Those are real fixes; the restriction gate remains incomplete.

Blockers:

  • apps/ios/Domain/Allergens/AllergenGroupMembership.swift:280-287 implements unreadable preferences by excluding only IDs 1-50. RecipeRepository.swift:126-149 accepts every other ingredient ID unless individually excluded. A recipe made from catalog/custom IDs outside that set can therefore pass after the group check fails. Valid milk avoidance has the same gap for an unlisted compound such as cream cheese. Reject the recommendation when preferences are unreadable, and evaluate every ingredient against canonical facts when preferences are valid. Unknown composition must remain unknown. The new test at Tests/AllergenFailClosed/AllergenPreferenceFailClosedTests.swift:131-139 explicitly pins the limited ID universe; add recipe-level non-core cases.
  • apps/ios/Domain/Models/HealthProfile.swift:102-107,150-153 still turns malformed individual-exclusion JSON into no exclusions. With valid empty groups, a corrupt stored individual milk exclusion allows milk recipes and does not request reconfirmation. Classify individual exclusions too, preserving a failed-check state instead of treating a parse failure as an empty selection. Dietary JSON at lines 86-90 has the same fallback.
  • apps/ios/Tests/MigrationUpgradeTests.swift:18-25 still expects a migration suffix ending at v20 while Migrations.swift:672 registers v21. The reported a6a02b5 run executed 432 tests with 63 assertion failures: 61 intake-inference assertions, this migration assertion and LaunchPerformanceRemediationTests.swift:50. These sources are unchanged at the reviewed head. Correct the joined migration expectation and record the remaining failures accurately. The older Handoff claim that intake failures are the only red tests is still false.

Lesser findings:

  • HealthProfile.swift:189-197 still enforces vegan exclusions with five dairy IDs. Other animal ingredients depend on recipe tags, and unknown diets are dropped by the supported-ID selection. The new JSON-group fix does not establish fail-closed diet enforcement.
  • docs/integration/CONFLICTS.md, C4, still says the v20 suffix is the only correct joined expectation, despite C5 and the ledger recording v21. Update the conflict decision and combined-head evidence together.
  • The pin correction addresses the newly added redistribution. The unchanged live apps/ios/Resources/data.json still has 166 recipe rows and 141 BBC URLs; this review does not establish release rights for that inherited corpus.

Task criteria, 2/6 met:

Criterion Status and evidence
PR ledger with head, disposition and reason Partly met: detailed saved roster exists; its snapshot omits subsequent overhaul PRs.
Linux package build/test acceptance Met on the reported checks: root suite 26/26, refresh 23/23, historical suite 14/14 before these unrelated changes; latest pin tests 10/10.
Real macOS CI result and merge-break repair Partly met: real CI runs exist; joined migration failure remains and latest run is pending.
Conflict record with both sides and resolution Partly met: C1-C5 exist; C4's expectation contradicts the v21 join.
Workflow and contributing-branch ownership boundary Met: no workflow delta from alpha; changes stay on the integration branch.
Binding safety and recipe-rights acceptance Partly met: new unlicensed pin text removed; restriction failure paths above remain.

Update after CI completion: REST build-and-test at this exact head failed with 432 tests and the same 63 assertion failures, including MigrationUpgradeTests.swift:18 and LaunchPerformanceRemediationTests.swift:50. The reviewed safety and migration blockers remain. Source and CI review only; no local tests, Xcode or simulator runs.

SamGu-NRX and others added 3 commits October 12, 2026 00:54
The merged trunk registered v21_bundle_ownership in the setup migration
sequence, so an upgrade of the last-shipped (v15) schema applied v16-v21
during setup while MigrationUpgradeTests pins the setup suffix at v16-v20
with v21 applied by the bundle-ownership refresh stream: one database
ended up with two different orderings depending on which stream ran
first, and hydration checks disagreed with the upgrade path.

Reconcile the ordering contract: register v21 in the setup sequence only
when the database is not coming from the shipped v15 schema, and let the
ownership refresh stream apply the extracted v21 schema migration
idempotently. Fresh installs still end at v21; v15 upgrades apply
v16-v20 at setup and v21 on first bundled refresh. Restore the
zero-argument warmBundledContentIfNeeded() launch contract, which the
ownership stream needs for the deferred schema to exist before hydration.

Adds MigrationOrderReplayTests replaying the migration-order contracts
against the real migrator: fresh install, v15 upgrade, v18 upgrade, and
idempotent ownership-schema application.

Verified on Linux: root package suite 20/20, historical-nutrition-check
14/14, bundled-refresh-check 23/23.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…are tokens

InventoryIntakeInferenceTests pins the intake inference semantics that
the CI log showed failing 61 assertions:

- A recognized storage word in the bundled tip wins over the name
  ("Refrigerate after opening" files soy sauce in the fridge even though
  the name says sauce-pantry), and the tip means its EARLIEST recognized
  word: "Refrigerate or freeze" is fridge, "Cool dark place, not fridge"
  is pantry, "Room temp until ripe, then fridge" is pantry.
- "not fridge" and other negative constructions rule locations out
  instead of selecting them.
- A tip that names no recognized storage word is ignored, and a missing
  tip falls back to the name instead of returning .unknown.
- Name matching is whole-token and plural-aware ("peas" never fires the
  "pea" rule for "peanut"/"peach"; "green beans" is not "bean"), with
  pinned collision overrides (eggplant, red pepper flakes, olive oil,
  goat cheese, dried vs fresh beans, cilantro) and OCR-style underscores
  treated as spaces.

Verified: transcription-checked against the test's tables with a
202-check scratch harness (0 failures); native CI re-runs the 61
assertions on macOS at push.

Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…X/FridgeLuck into fleet/fl-trunk-20261011

# Conflicts:
#	apps/ios/Package.swift

@SamGu-NRX SamGu-NRX left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: BLOCK

Reviewed 96c290d81460a704052ffdb32523326dd07ba222 against fl-integration-f1 Part 1 and plan #86. The previous native-CI failures are resolved: REST logs show 436 tests, zero failures at this exact head. The allergen gate remains incomplete, and the changed intake inference still promotes guessed storage conditions into expiry dates.

Blockers:

  • apps/ios/Domain/Allergens/AllergenGroupMembership.swift:280-287 excludes only the curated 50 IDs when group preferences are unreadable. Platform/Persistence/Repository/RecipeRepository.swift:126-149 allows recipes containing other catalog or custom IDs unless individually excluded. A failed group check therefore still allows recommendations outside that universe. Valid avoidance also misses unlisted compounds. Reject recommendations after unreadable preferences and require canonical composition evidence for every ingredient, preserving unknown composition.
  • apps/ios/Domain/Models/HealthProfile.swift:102-107,150-153 converts malformed individual-exclusion JSON to no exclusions. With valid empty groups, a corrupt saved milk exclusion allows milk recipes without reconfirmation. Classify this parse failure explicitly. Dietary JSON at lines 86-90 has the same fallback.
  • apps/ios/Platform/Persistence/Services/InventoryIntakeService.swift:254-279,329-332 treats the first matching word in a storage tip as the actual location. "Keep cool and refrigerated" resolves to pantry. "Use within 2 days or freeze" resolves to freezer without evidence that the food was frozen; Tests/InventoryIntakeInferenceTests.swift:108-109 explicitly pins this mistake. The main scan supplies .inferredFromIngredient at Feature/Ingredients/IngredientReviewView.swift:71, and InventoryRepository.swift:80-89,784-788 assigns fallback pantry/freezer expiries of 30/90 days. That is materially different from the 1-2 refrigerated days specified for fresh poultry. Conditional, negated or ambiguous advice must not establish a storage fact. Require location confirmation or retain unknown expiry.
  • InventoryIntakeService.swift:286-291,344-347 evaluates collision overrides before the explicit frozen token. "Frozen coconut milk" becomes pantry and "frozen green beans" becomes fridge. The previous frozen-first path did not have this regression. Feature/Inventory/GroceryPendingItem.swift:37 uses this result for pending grocery items. Preserve explicit preparation/storage evidence before generic ingredient overrides and add both cases.

Lesser findings:

  • Migrations.swift:673-680 now defers v21 only when the starting database ends at v15. The four new replay tests cover that split and helper idempotence. Update docs/integration/CONFLICTS.md:25-35 to explain the resulting version-dependent registration and the joined launch/refresh contract; its C4/C5 descriptions still describe the earlier unconditional sequence.
  • HealthProfile.swift:189-197 still uses a small dairy-ID set for vegan exclusion and drops unsupported diet identifiers. The group parser change does not establish complete diet enforcement.
  • The rights correction from the preceding review is unchanged. This delta adds no recipe text; release rights for the inherited live recipe bundle remain unestablished.

Task criteria, 3/6 met:

Criterion Status and evidence
PR ledger with head, disposition and reason Partly met: saved roster still omits subsequent overhaul PRs.
Linux package build/test acceptance Met on reported current-head checks: root 30/30, historical 14/14, refresh 23/23; preceding pin checks are unchanged.
Real macOS CI result and merge-break repair Met: native build-and-test passed 436 tests at this SHA.
Conflict record with both sides and resolution Partly met: C1-C5 exist; the new migration split needs a recorded rationale.
Workflow and contributing-branch ownership boundary Met: no workflow delta from alpha; reviewed changes stay on the integration branch.
Binding safety and recipe-rights acceptance Partly met: allergen failures and storage/expiry errors remain.

Native CI evidence was read through REST, including its completed test log. Source and assertions were inspected; no local tests, Xcode or simulator were run.

@SamGu-NRX

Copy link
Copy Markdown
Owner Author

Native CI at 96c290d: ✅ success — run 38190012205 (iOS CI): build-and-test completed with the Build and Test step succeeding, and dead-code-advisory also success.

This is the same workflow and job that failed the 63 assertions at 4683161 (run 38156342266). The assertions ran unmodified: the 61 InventoryIntakeInferenceTests failures are fixed by 644f3bc, the LaunchPerformanceRemediationTests source contract by the AppDatabase overload restoration in 3b7231e, and the MigrationUpgradeTests upgrade-tail order by 3b7231e (v21 deferred out of the v15 setup stream, applied by the ownership-refresh stream instead). Linux surface evidence is in the Proof - migration-order fix (2026-10-12/2026-10-11) section above: root package suite 30/30 on the merged tree (16 InventoryInvariants + 4 new MigrationOrderReplayTests + 10 AllergenFailClosed from the parallel writer), historical-nutrition-check 14/14, bundled-refresh-check 23/23.

@SamGu-NRX

Copy link
Copy Markdown
Owner Author

Join verification - review fixes (2026-10-12)

Parent-coordinator verification across the three remediation subthreads; all three review blocks on this PR are remediated. Full fix proofs remain in the PR body above (append-only).

  • Recipe text (c304f50): the trunk diff adds zero recipe instruction text - the pin ships recipes: [] with ingredients/tags byte-identical and recomputed manifest hashes; the pre-existing alpha bundle blob is unchanged; the rights rule is enforced fail-closed in the pin checker, with --omit-recipes keeping the pin reproducible.
  • Allergen preferences (a6a02b5): malformed JSON and unknown group IDs fail closed (curated-core exclusion, empty picker prefill, reconfirmation); valid selections filter byte-identically to before.
  • Migration order (3b7231e + 644f3bc): v21 registration is skipped on v15 upgrades with the schema applied from the ownership-refresh stream; intake tokenization repaired; MigrationOrderReplayTests added; no test expectations modified. Native CI run 38190012205: build-and-test success on head 96c290d (63 failures at 4683161 to 0).

Parent API verification: every fix commit is authored by Sam Gu 127461594+SamGu-NRX@users.noreply.github.com with the required obvious-autobuild[bot] co-author trailer. Pull-created merge commits (96c290d, 5c52a70) keep git default messages without the trailer (author Sam Gu); correcting them would rewrite published history, which is forbidden. Linux baselines on the merged tree: root 30/30, historical-nutrition 14/14, bundled-refresh 23/23, data-pipeline pytest 10/10.

Open flag for follow-up (deliberately outside the review block): parsedAllergenIds (allergen_ingredient_ids) still parses malformed input to [] - the same fail-open shape, left for a follow-up fix. Native render/Dynamic Type/VoiceOver remain for the operator pass. PR remains open draft for re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants