Skip to content

[obvious-fleet] fix(ios): reviewed substitution table as bundled app data — compiles in macOS CI - #154

Draft
obvious-autobuild[bot] wants to merge 3 commits into
obv/burst5-fridgeluck-spec-lecheffrom
obv/burst5-fridgeluck-lechef-substitutions
Draft

obvious-autobuild[bot] wants to merge 3 commits into
obv/burst5-fridgeluck-spec-lecheffrom
obv/burst5-fridgeluck-lechef-substitutions

Conversation

@obvious-autobuild

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

Copy link
Copy Markdown

fleet-packet: burst6-fridgeluck-fix154-ci

Reviewed substitution table → bundled app data (+ macOS CI fix)

Draft PR #154, base obv/burst5-fridgeluck-spec-lechef, head 452a424 on obv/burst5-fridgeluck-lechef-substitutions. Part of the LeChef overhaul burst 5. Adopts PR #114's reviewed substitution table (279 pairs, branch overhaul/substitution-table) as bundled app data, mapped to app ingredient IDs without inventing identities, and wires the app's SubstitutionService to consume it.

1. What changed and why

The CI fix (this packet, commit 452a424): apps/ios/Platform/Persistence/Services/SubstitutionService.swift line 2 imported a module that does not exist — import FeatureLogic. The framework target in project.yml is FLFeatureLogic (framework, PRODUCT_NAME: FLFeatureLogic), so the app-target import must be import FLFeatureLogic, as every other app/framework-boundary file in the repo already does. The wrong import was the only error in the first CI run (run 38724597220, head e6b8ec0, SubstitutionService.swift:2:8: error: Unable to resolve module dependency: 'FeatureLogic'); CI stops at the first file that fails to compile, so nothing after it had been compiled. One line, no behaviour change, no other files touched.

The slice itself (commits f6cbc5c, e6b8ec0, carried forward unchanged):

  • scripts/substitutions/convert.py + test_convert.py — pairs.json → app-facing resource converter.
    • Endpoint→app-ID mapping uses exact evidence only: catalog name, recorded alias, or fdc id. No fuzzy matching; ambiguous evidence (two app ingredients claiming the same fdc id, no unique name hit) fails closed to unmapped.
    • Pairs with an unmapped endpoint are dropped and counted in a mapping audit; --strict exits 2 on any unmapped ingredient.
    • Role fits carry per-role evidence verdicts exactly per SCHEMA.md's ContextRules table (verified/plausible/unknown/unsupported; conflict stays plausible-at-best; unknown is never upgraded). A functional fail on the only role drops the pair; on another role it lands in excludedRoles. A conditional fail rides separately in conditionalFor without degrading the swap's own verdict.
    • A supported_also role never inherits the primary ratio — a missing ratio row for it stays unknown.
    • Recheck payloads carry the substitute's full compound allergen/diet class closure (soy sauce → soy + gluten) and compositionKnown from the canonical facts store; a null facts_key fails closed to ask-the-user even when the ingredient mapped.
    • Guards: difference text promising "no change" is rejected; every pair must cite a source; output is deterministic (keys sorted, verified by a byte-equality test).
  • apps/ios/Resources/Substitutions/substitutions-v1.json — generated resource: 35/279 pairs shipped, 40/317 referenced ingredients mapped, 244 pairs dropped fail-closed, with the source commit (4b58954 on overhaul/substitution-table) recorded in source. A pinned copy of pairs.json is committed under data/substitutions/ for reproducibility (see scripts/substitutions/README.md for the regeneration commands).
  • apps/ios/FeatureLogic/Substitution/SubstitutionTable.swift — pure-logic lookup and per-eater composition screen in the FLFeatureLogic framework: allowed / askUser (unknown composition, only under active restrictions) / refused(offending:) naming the offending classes. Compound closures are included in the screen; unknown restriction IDs (keto, low_carb) screen nothing — the reviewed table defines no forbids for them. Public API only (no GRDB) so the app target consumes it across the module boundary.
  • apps/ios/Platform/Persistence/Services/SubstitutionService.swift — loads the bundled table at init; the hardcoded 24-pair map is removed. Existing API (substitutions(for:dietaryRestrictions:), hasSubstitutions(for:), ingredient(id:)) is kept compatible; a new screenedSubstitutions(for:dietaryRestrictions:) exposes ask-user states instead of silently dropping swaps. A missing or undecodable resource leaves the service empty (nothing wrong is ever offered), with an assertion failure in debug. The bundle lookup tries the bundle root first, then a Substitutions subdirectory, so either XcodeGen resource layout works.
  • apps/ios/Tools/substitution-check/ — portable check that compiles the production SubstitutionTable.swift against the generated resource.

2. How I verified it

  • macOS CI (the compiler and test runner for this PR — Xcode 26.6, iPhone 17 Pro simulator, builds the XcodeGen project and runs apps/ios/Tests):
    • Failing: head e6b8ec0, run 38724597220 — build-and-test failed with SubstitutionService.swift:2:8: error: Unable to resolve module dependency: 'FeatureLogic'.
    • Passing: head 452a424, run 38767497722 — build-and-test pass (4m45s), dead-code-advisory pass (2m50s). Both conclusions re-verified via gh run view ... --json headSha,conclusion.
  • python3 -m pytest scripts/substitutions/ -q → 24 passed (schema gate, mapping by name/alias/fdc, ambiguous-fdc fail-closed, no-fuzzy, pair-drop isolation, strict mode, verdict matrix, functional/conditional fails, supported-also ratio isolation, compound closure, diet classes, source/difference guards, determinism, restriction shipping). Run on head 452a424 even though the fix does not touch the table or converter.
  • SWIFT_BIN=.../swiftc bash apps/ios/Tools/substitution-check/run.sh (Swift 6.1.2, Linux sandbox) → OK — 35 swaps, 40 mapped ingredients, sweep allowed=435 askUser=0 refused=20. The sweep runs every shipped swap × every table restriction and asserts a forbidden class never screens allowed, refusals name exactly the offending classes, unknown-composition swaps ask the user, and the compound cases hold (olive-oil→butter refused for vegans naming dairy; tofu substitutes refused for soy-free but allowed for gluten-free). Behaviour identical to pre-fix evidence — the one-line import fix did not change the slice.
  • swiftc -parse on SubstitutionService.swift → clean (syntax; type checking happens in macOS CI).
  • No existing test references SubstitutionService or the old hardcoded map (grep over apps/ios/Tests/), so the hardcoded-map removal breaks no encoded expectation; no test was changed.

3. What is not verified

  • No Xcode in this sandbox: the iOS app target and the FLFeatureLogic framework were compiled only by hosted macOS CI. SubstitutionService type-checks only in CI (GRDB + the framework import); locally I could syntax-parse it and compile the pure framework file (SubstitutionTable.swift) via substitution-check. The app build, unit tests, and simulator run happened only on CI, and only build-and-test results are cited.
  • SubstitutionSheet.swift UI is untouched — it consumes the same Substitution shape; surfacing ask-user rows in the sheet is a follow-up.
  • No device run; the bundled resource is verified to decode deterministically by substitution-check, not by running the app.

4. Assumptions

  • The table's own runtime package (packages/FLSubstitutions) is PR Overhaul: reviewed substitution table (data milestone 1) #114's deliverable, not this slice's; this PR consumes the generated JSON only, so the app does not take a dependency on that package in this PR.
  • Unknown app restriction IDs (keto, low_carb) are treated as screen-nothing: the reviewed table defines no forbids for them, so they neither allow nor refuse a swap here.
  • Committed data/substitutions/pairs.json is a pinned snapshot of PR Overhaul: reviewed substitution table (data milestone 1) #114's table at 4b58954; regeneration against a newer table requires updating the --source-commit accordingly.
  • The single CI failure was the whole compile story: since CI stops at the first failing file, I could not pre-verify later files locally, so I pushed the one-line fix and let build-and-test (which passed with no further errors) confirm it.
  • Branch stays unmerged; the integration thread (burst6-fridgeluck-trunk) picks it up once green.

Human author: Sam Gu (sgu07966@gmail.com)

🤖 Generated with Obvious

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

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

SamGu-NRX and others added 2 commits October 15, 2026 11:02
Adopt PR #114's reviewed substitution table (279 pairs) as bundled app
data, mapped to app ingredient IDs without guessing:

- scripts/substitutions/convert.py: pairs.json -> app-facing resource.
  Maps endpoints by exact name, alias, or fdc id only (no fuzzy match);
  ambiguous evidence fails closed; pairs with unmapped endpoints are
  dropped and counted in a mapping audit. Role fits carry per-role
  evidence verdicts (verified/plausible/unknown/unsupported; unknown is
  never upgraded); supported_also roles never inherit the primary ratio;
  conditional fails ride separately from functional fails. Recheck
  payloads carry the substitute's full compound allergen/diet closure
  and compositionKnown from the canonical facts store. 24 tests.
- apps/ios/Resources/Substitutions/substitutions-v1.json: generated
  resource (35/279 pairs, 40/317 ingredients mapped, 244 dropped
  fail-closed) from overhaul/substitution-table at 4b58954.
- apps/ios/FeatureLogic/Substitution/SubstitutionTable.swift: pure-logic
  lookup and per-eater composition screen (allowed / ask-user on unknown
  composition / refused with offending classes).
- apps/ios/Platform/Persistence/Services/SubstitutionService.swift:
  loads the bundled table; hardcoded 24-pair map removed; existing API
  kept, screenedSubstitutions added for ask-user states.
- apps/ios/Tools/substitution-check: portable check compiling the
  production logic against the generated resource (schema gate, audit
  counts, determinism, all-pairs x all-restrictions sweep, compound
  refusals, verdict honesty).
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 15, 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: 4860e3ec-2bde-4ac6-bfaf-c97613cd5104

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.

@SamGu-NRX
SamGu-NRX changed the base branch from main to obv/burst5-fridgeluck-spec-lechef October 15, 2026 11:19
The framework target in project.yml is named FLFeatureLogic (product name
FLFeatureLogic), so the app-target import must be 'import FLFeatureLogic'.
The previous 'import FeatureLogic' does not resolve and failed build-and-test
on macOS CI (run 38724597220) at head e6b8ec0.

No behaviour change: same table, same screen outcomes.

🤖 Generated with [Obvious](https://obvious.ai)

Co-Authored-By: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
@obvious-autobuild obvious-autobuild Bot changed the title feat(ios): reviewed substitution table as bundled app data [obvious-fleet] fix(ios): reviewed substitution table as bundled app data — compiles in macOS CI Oct 15, 2026
@obvious-autobuild

Copy link
Copy Markdown
Author

Independent review (Obvious, Autobuild + Max)

Verdict: ready. The slice does what the body claims — every number I could reproduce reproduced exactly, including the CI story, the audit arithmetic, and the checker sweep. The findings below are all low severity (two one-liners in the new tooling/docs, one coverage gap), none touch shipped app behavior.

Transparency note: workspace context says this PR was authored by this workspace's own fleet in an earlier burst. I treated the body as untrusted and re-derived every claim below live at head 452a4244; nothing was taken from the body on trust.

Findings (ranked by severity)

1. Low — apps/ios/Tools/substitution-check/run.sh:12 — the documented check fails on any fresh environment.
BUILD_DIR="$(mktemp -d /tmp/fl-tc/substitution-check/build.XXXXXX)" requires /tmp/fl-tc/substitution-check to pre-exist. Verified: after rm -rf /tmp/fl-tc, the script exits 1 with mktemp: failed to create directory via template '/tmp/fl-tc/substitution-check/build.XXXXXX': No such file or directory; after mkdir -p /tmp/fl-tc/substitution-check it runs green. So the body's successful run implicitly relied on a leftover scratch dir, and anyone following the README's "Tests and the portable check" on a fresh clone or CI runner gets a cryptic failure. Smallest fix: mkdir -p "$(dirname "$BUILD_DIR")" before the mktemp (or mktemp -d "${TMPDIR:-/tmp}/substitution-check.XXXXXX" and drop the fixed parent).

2. Low — scripts/substitutions/README.md:23 — regeneration pins the branch tip, not the recorded commit.
The command is git show origin/overhaul/substitution-table:data/substitutions/pairs.json while --source-commit records 4b5895409…. If that branch advances, a regeneration following the README emits a resource whose source.sourceCommit mislabels provenance. Smallest fix: git show 4b5895409cb1c990a5e8251f490fe80ccd35c295:data/substitutions/pairs.json. (The committed pairs.json is byte-identical to that commit today, so the shipped artifact is correct.)

3. Low — SubstitutionService has no automated coverage, including its bundle lookup (SubstitutionService.swift:175).
grep -rn "SubstitutionService" apps/ios/Tests/ → no references (the body discloses this). macOS CI green proves the file compiles and existing XCTests pass; substitution-check compiles only the framework-side table. Nothing asserts the resource actually resolves through Bundle.main (root vs Substitutions/ subdirectory — both coded for), and nothing exercises screenedSubstitutions or the fail-closed empty state. Suggested follow-up: one XCTest asserting the bundle resource resolves and decodes, ideally before screenedSubstitutions gains a UI consumer.

Things I checked and am satisfied by (not findings): the untested function_status == "unsupported" verdict branch ships in no swap (sourced/convention/unknown only); no shipped swap carries conditionalFor entries, so the app not surfacing conditional refusals is moot today (3 swaps carry excludedRoles — role-level function caveats, not allergen screens — and the allergen/diet screens are enforced); the legacy ratio field receives the kitchen-measure value for non-scaling bases, compensated by the note text ("Measure per the kitchen statement…"), consistent with the untouched SubstitutionSheet.

What I verified (commands and results)

  • PR state via gh pr view 154 --json … / --comments: draft, base obv/burst5-fridgeluck-spec-lechef@615fc1b6, head obv/burst5-fridgeluck-lechef-substitutions@452a4244; 3 commits; 9 files (+20,888/−133); no workflow files touched; only PR comment is CodeRabbit's review-skipped boilerplate — no human review yet.
  • CI story re-verified live: gh run view 38767497722 --json headSha,conclusion → success @ 452a42447d…; gh run view 38724597220 → failure @ e6b8ec0e3d…; gh pr checks 154 → build-and-test pass 4m45s, dead-code-advisory pass 2m50s. Matches the body exactly.
  • Import-fix rationale: grep -rn "^import FeatureLogic" apps/ios → 0 hits; 30 files already import FLFeatureLogic; project.yml:30 PRODUCT_NAME: FLFeatureLogic; the FLFeatureLogic sources glob (project.yml:20) covers the new SubstitutionTable.swift; apps/ios/Resources (project.yml:70) ships the new JSON.
  • python3 -m pytest scripts/substitutions/ -q → 24 passed.
  • Reproducibility: regenerated the resource with python3 scripts/substitutions/convert.py --table data/substitutions/pairs.json --app-data apps/ios/Resources/data.json --out … --source-commit 4b5895409… → reported 35/279 pairs, 40/317 mapped, 244 dropped, and cmp says byte-identical to the committed substitutions-v1.json; git show 4b5895409…:data/substitutions/pairs.json | cmp - data/substitutions/pairs.json → byte-identical (279 pairs).
  • bash apps/ios/Tools/substitution-check/run.sh (Swift 6.1.2, Linux) → OK — 35 swaps, 40 mapped ingredients, sweep allowed=435 askUser=0 refused=20 (= 35 swaps × 13 restrictions) — matches the body. Negative control: deleting dairy from restrictions.vegan.forbids makes it fail (olive-oil-fat-to-butter: must refuse for vegan eaters, exit 1) — the sweep asserts real semantics rather than echoing the document.
  • Revert ritual on the fix: flipping the import back to import FeatureLogic leaves swiftc -parse clean (parse never resolves modules — which is exactly why this escaped to macOS CI); file restored, worktree clean.
  • Spot checks: data.json ids 1=egg, 20=banana (the checker's egg→banana example is real); audit arithmetic closes (35+244=279; 40+277=317); 13 restrictions with no keto/low-carb rows (unknown ids screen nothing, as documented); shipped fit verdicts verified=8 / plausible=13 / unknown=31.

What I couldn't verify

  • App-target type-check and XCTest — no Xcode in this sandbox; compile/test evidence is the green hosted macOS run only (the body says the same).
  • The SCHEMA.md ContextRules table — it lives on overhaul/substitution-table (PR Overhaul: reviewed substitution table (data milestone 1) #114), not this branch, so the verdict matrix was checked against the converter's tests, not the schema source.
  • Runtime behavior in the app: SubstitutionSheet is untouched, no device run, and the resource's presence in the built bundle is reasoned from the project.yml resources glob plus the double bundle lookup, not runtime-proven.
  • data/substitutions/pairs.json (~17.5k lines) and substitutions-v1.json (~2k lines) were verified via the byte-identical regeneration, audit arithmetic, jq spot checks, and the checker sweep rather than line-by-line reading — regeneration from the pinned, byte-identical source is the stronger guarantee anyway.

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.

1 participant