Skip to content

feat(cook): LeChef swaps execute or refuse with a receipt the gateway accepts - #191

Merged
SamGu-NRX merged 11 commits into
alphafrom
obv/burst10-fridgeluck-lechef-receipt
Oct 16, 2026
Merged

SamGu-NRX merged 11 commits into
alphafrom
obv/burst10-fridgeluck-lechef-receipt

Conversation

@obvious-autobuild

@obvious-autobuild obvious-autobuild Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

What changed and why

The receipt contract's allergenRecheck is an object — verdict | eaters | optional blockedPath — 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/plausible allow; unsupported and unknown refuse).
  • 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 to nil, 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, returns unsupported for known allergen/diet conflicts (with the blocked path named) and unknown when profile or composition knowledge cannot be settled. LiveAssistantView installs the shared service before the live session starts.
  • Live-protocol fixtures: two new receipts recorded from the real gateway — receipt-executed-swap.json (verified, blockedPath: null) and receipt-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.
  • LeChef socket walk: the scripted scenario now exercises both outcomes over the real WebSocket — butter → soy sauce refuses (wheat blocked path named in the receipt), butter → olive oil executes with a clean screen — and the backend boot asserts both receipts, including that the gateway echoes the verified recheck in the resolved tool call.

How I checked it (commands and results)

  • swift build of the walk package (Swift 6.4, Linux): clean — it compiles the real pinned production CookMode sources, so CookSubstitutionRecheck and 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 → Linux swift build of 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, including S4 rejected receipt carries the contract-shaped allergen recheck and S4b 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 --strict on every changed Swift file: clean. Formatting in place also re-wrapped a handful of pre-existing lines in CookModeViewModel.swift and SubstitutionService.swift (formatter-canonical; CI does not run swift-format) — those hunks are style-only.
  • swiftc -parse on every changed/added Swift file: OK.
  • Fixture byte round-trip: decode → re-encode of receipt-executed-swap.json / receipt-rejected-swap.json reproduces the recorded bytes exactly; the executor replay of both swap proposals produces those same envelopes.

What I could not check

  • Linux never ran XCTest — the full package and app test target need Apple frameworks; hosted macOS CI validated all new/updated suites, see below.
  • No Xcode/device run — the live-assistant swap UI path was not exercised on a phone; the walk is the Linux proxy for the protocol path.
  • The walk's fake phone logs eaters from its scripted snapshot (["Ana"]); production sessions start with empty eater sets and the receipt falls back to ["household"] — not observed end to end on device.

Assumptions

  • The walk and boot scenario keeps its committed turn texts; TURN_SWAP_OK ("olive oil instead of butter then") drives the allowed swap with substitute id 9002 from the reviewed table.
  • unsupported is the right verdict for a known allergen-blocked pair (consistent with the backend fake phone's mapping); plausible is reserved for pair evidence that is only plausible.
  • Re-pinning the e2e walk's source hashes (refresh.sh) is the documented step after changing pinned production sources; CookSubstitutionRecheck.swift joined the pin list because the receipt decoder now references it.

Hosted macOS CI trail

GitHub dropped the pull_request trigger for later pushes to this branch, so rounds 3–4 ran as manual workflow_dispatch on the branch head.

Head Run Result
c7ffca54 — compile fix in the flow tests (NoopTimerScheduler scope) → 16eed489
16eed489 38853958501 compiled, full suite ran: 2 failures in my flow tests — the test harness accepted the recheck provider but never passed substitutionRecheck: into Dependencies → 6c140580; plus the base-borne set below
6c140580 38856323233 (dispatch) compile failure in untouched PlanDataAdapter.swift (cannot find 'bridged') — my merge base 70483b3f never compiled (round 2's PR merge-ref had silently borrowed the base's fixed file)
86dcbfdc 38857999071 (dispatch) compiles clean; every test this PR adds or edits passes — CookSubstitutionRecheckTests, CookSubstitutionRecheckFlowTests, LiveProtocolFixtureTests, CookProposalWireAndExecutorTests, LeChefPlanPersistenceTests all green. Round 4 merges the repaired rejoin base tip 38f76169; one conflict (SubstitutionService.swift) kept the base's canonical safety-gates structure and re-added the receipt-recheck section, verified to touch no changed signatures

Remaining failures at 86dcbfdc are all base-borne and reproduce at the base tips (run 38856653954 on 38f76169; the sweep still fails at the newest rejoin tip 78380eee): 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

…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>
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4914ec7f-9e7b-41c7-a716-a7ab679872b3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ObviousApp and others added 3 commits October 16, 2026 05:05
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>
@SamGu-NRX
SamGu-NRX changed the base branch from obv/burst9-fridgeluck-rejoin to alpha October 16, 2026 06:53
SamGu-NRX and others added 7 commits October 16, 2026 02:02
…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>
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>
@SamGu-NRX
SamGu-NRX marked this pull request as ready for review October 16, 2026 08:18
@SamGu-NRX
SamGu-NRX merged commit 9e4a3af into alpha Oct 16, 2026
3 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T08:20:20.757250Z 0e3d855 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codemetrics-ai

Copy link
Copy Markdown

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.

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