Repository navigation
feat(cook): LeChef swaps execute or refuse with a receipt the gateway accepts - #191
Conversation
…ccepts
The receipt contract's allergenRecheck is an object (verdict | eaters |
optional blockedPath); the phone emitted an incompatible {blocked,
checked} shape and the gateway could not accept its receipts.
- CookSubstitutionRecheck: the contract-shaped recheck with canonical
JSON in contract key order and fail-closed swap eligibility
- LiveProposalFrames: decode the recheck object strictly (unknown
verdicts, missing keys, and non-string eaters are malformed, never a
guessed pass) so decode -> re-encode is byte-identical
- CookModeViewModel.applySubstitution: run the full recheck through the
injected provider, fail closed on unknown screening, and record the
canonical recheck on executed and refused swaps alike
- SubstitutionService: share the household safety resolver for the
recheck, recording screened eaters and pair-evidence verdicts
- LiveAssistantView: install the shared service recheck before the live
session starts
- Live-protocol fixtures: record receipt-executed-swap (verified) and
receipt-rejected-swap (unsupported) through the real gateway and pin
both protocol ends to the same bytes
- LeChef walk: exercise both outcomes over the real WebSocket — the
soy-sauce swap refuses with the blocked path named, the olive-oil
swap executes with a clean screen; boot asserts both receipts
Co-authored-by: Sam Gu <sgu07966@gmail.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 |
NoopTimerScheduler is nested in CookModeViewModel, not top-level, so the bare reference failed the macOS build. Co-authored-by: Sam Gu <sgu07966@gmail.com>
makeViewModel accepted the recheck closure but never wired it into the Dependencies init, so init-time-provider tests fell through to the unknown verdict instead of exercising their providers. Co-authored-by: Sam Gu <sgu07966@gmail.com>
…branch Resolves SubstitutionService.swift in favor of the base's canonical safety-gates structure (screenedOffers, re-entrancy-safe substitutions variant, bridged-aware verdicts) and re-adds the receipt-recheck section, which touches none of the changed signatures. Verified after the merge: swiftc -parse, swift-format lint --strict, LeChef walk pins + run 14/14, backend suite 338/0. Co-authored-by: Sam Gu <sgu07966@gmail.com>
…ck-lechef-receipt # Conflicts: # apps/ios/Platform/Persistence/Services/SubstitutionService.swift
…creen The weekly plan screened catalog candidates by composition class only and left explicit allergens to the allocator's eater filter. That filter sees one id per line (the smallest id the bridge maps the key to) and required lines only. So a household that flagged an ingredient was still planned a recipe whose line bridged to that ingredient under a second id, or used it as an optional line, and diet households got legacy recipes without the diet tag and catalog recipes without the diet claim. Recommendations refuse all of these. Move the catalog admission rule into HouseholdSafetyProfileResolver.admitsCatalogRecipe (composition screen, excluded ids over every bridge row of every line, diet claim gate) and call it from both catalogMatches and loadCatalogCandidates. The legacy plan pool now applies the recommend path's tag mask and whole-recipe id exclusion (stored allergens plus diet-excluded ids). Blocked recipes leave the snapshot instead of riding to the allocator, so two plan tests that asserted the old raw-inventory snapshot now assert the removal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The live chat's cook session kept the ephemeral empty swap list, so every swap proposal found no pair and was refused as "substitution unavailable" before the installed household recheck could run. installHouseholdRecheck now also installs the service's household-screened swaps, as the cooking guide does. The recheck returned unknown for an unbridged substitute even when the household restricts nothing, while the offer screen allows that substitute since alpha's ee38b90; an offered swap would then be refused. Return verified there, as the unknown-classes branch already did. Tests cover the service recheck (explicit exclusion, unread profile, a vegan household over the shipped bundle where every offered swap passes the recheck and a dairy substitute is refused with the diet named) and the live chat executing an offered swap once the service is installed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ck-lechef-receipt
Follow-up to b9405084 after merging alpha's guest meal planner, which reads PlanDataAdapter.loadSnapshot and keeps a legacy recipe whose only flagged line is optional, minus that line. b9405084 dropped such recipes outright. A required line on an excluded ingredient still removes the recipe; an optional one is now stripped in the snapshot, so neither the weekly plan nor the guest planner lists it. Catalog recipes keep whole-recipe exclusion, as the composition screen and recommendations apply. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ck-lechef-receipt
…ck-lechef-receipt
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add FridgeLuck to your team's repositories. |
What changed and why
The receipt contract's
allergenRecheckis an object —verdict|eaters| optionalblockedPath— but the phone emitted an incompatible{blocked, checked}shape, so the gateway could never accept a swap receipt from the device. This change makes cook swaps execute or refuse end to end with a contract receipt:CookSubstitutionRecheck(new): the contract-shaped recheck. Emits canonical JSON in contract key order (the exact bytes the receipt encoder and the fixtures pin) and exposes fail-closed swap eligibility (verified/plausibleallow;unsupportedandunknownrefuse).LiveProposalFrames: the receipt decoder now reads the recheck object strictly — an unknown verdict, a missing key, or a non-string eater makes the whole receipt decode tonil, never a guessed pass. Decode → re-encode is byte-identical to the recorded fixtures.CookModeViewModel.applySubstitution: runs the full recheck through an injected provider, maps the legacy boolean screen to contract verdicts when no provider is installed, fails closed when screening is unknown or the session is inactive, and records the canonical recheck JSON on executed and refused swaps alike (a blocked attempt is still recorded, so the transcript shows the refusal, not silence).SubstitutionService.substitutionRecheck: the provider — shares the household safety resolver, records screened eaters, returnsunsupportedfor known allergen/diet conflicts (with the blocked path named) andunknownwhen profile or composition knowledge cannot be settled.LiveAssistantViewinstalls the shared service before the live session starts.receipt-executed-swap.json(verified,blockedPath: null) andreceipt-rejected-swap.json(unsupported,"Contains wheat via soy sauce") — with backend and iOS fixture trees pinned to the same bytes by the existing mirror test.How I checked it (commands and results)
swift buildof the walk package (Swift 6.4, Linux): clean — it compiles the real pinned production CookMode sources, soCookSubstitutionRecheckand the rewritten receipt decode type-check. The XCTest suites are the macOS CI's job — see "What I could not check".apps/ios/Tools/lechef-e2e/run.sh(pinned-source verify → Linuxswift buildof the walk compiling the real production CookMode sources →bun run check→ scripted gateway + walk over WebSocket): all walk checks passed; boot 14/14 checks passed, includingS4 rejected receipt carries the contract-shaped allergen recheckandS4b executed receipt carries the verified allergen recheck.cd backend/gemini-agent && bun test: 338 pass, 0 fail, 5011 expects across 23 files — includes the extended fixture recording test (gateway echoes both recheck verdicts; the fixture directory listing and the iOS mirror are pinned).swift-format lint --stricton every changed Swift file: clean. Formatting in place also re-wrapped a handful of pre-existing lines inCookModeViewModel.swiftandSubstitutionService.swift(formatter-canonical; CI does not run swift-format) — those hunks are style-only.swiftc -parseon every changed/added Swift file: OK.receipt-executed-swap.json/receipt-rejected-swap.jsonreproduces the recorded bytes exactly; the executor replay of both swap proposals produces those same envelopes.What I could not check
["Ana"]); production sessions start with empty eater sets and the receipt falls back to["household"]— not observed end to end on device.Assumptions
TURN_SWAP_OK("olive oil instead of butter then") drives the allowed swap with substitute id 9002 from the reviewed table.unsupportedis the right verdict for a known allergen-blocked pair (consistent with the backend fake phone's mapping);plausibleis reserved for pair evidence that is only plausible.refresh.sh) is the documented step after changing pinned production sources;CookSubstitutionRecheck.swiftjoined the pin list because the receipt decoder now references it.Hosted macOS CI trail
GitHub dropped the
pull_requesttrigger for later pushes to this branch, so rounds 3–4 ran as manualworkflow_dispatchon the branch head.c7ffca54NoopTimerSchedulerscope) →16eed48916eed489substitutionRecheck:intoDependencies→6c140580; plus the base-borne set below6c140580PlanDataAdapter.swift(cannot find 'bridged') — my merge base70483b3fnever compiled (round 2's PR merge-ref had silently borrowed the base's fixed file)86dcbfdcCookSubstitutionRecheckTests,CookSubstitutionRecheckFlowTests,LiveProtocolFixtureTests,CookProposalWireAndExecutorTests,LeChefPlanPersistenceTestsall green. Round 4 merges the repaired rejoin base tip38f76169; one conflict (SubstitutionService.swift) kept the base's canonical safety-gates structure and re-added the receipt-recheck section, verified to touch no changed signaturesRemaining failures at
86dcbfdcare all base-borne and reproduce at the base tips (run 38856653954 on38f76169; the sweep still fails at the newest rejoin tip78380eee):SafetyGatesSweepTests(restricted sweep, dropped-mapping, keto fail-closed, unknown-class mutation, unrestricted sees-recipes),SubstitutionServiceTests.testExplicitAllergenBlocksTheSubstituteEvenWithoutADiet,CommunityClientTests.testTransparentRefreshRefreshesTheAccountSnapshot,GoalPathTests.testFridgeToMealToTasteGoalPath. This PR touches none of those behaviors; the sweep/nonce repairs are owned by the rejoin and #187/#188 threads, which are actively pushing fixes.Human author: Sam Gu (sgu07966@gmail.com)
🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact