Repository navigation
Conversation
First milestone of the substitution-table overhaul: pairs.json, the reviewed, context-specific substitution table per SPEC §3.1/§3.5. - 279 pairs across all 11 functional roles (acid, fat, binder, leavening, thickener, liquid, aromatic, umami, sweetener, protein, garnish) - every pair carries: ratio with evidence status (sourced/convention/ conflict/unsupported/unknown), preparation changes, a plain-language flavour/texture difference, macro delta computed from USDA SR Legacy per-100 g values, allergen classes for BOTH sides including compound ingredients (Suite C vocabulary), and the roles where the swap fails - every pair is cited (23-entry source registry); conflicting shipped ratios are recorded as conflicts, never silently resolved (egg->mashed banana 0.6 vs ~1.2; honey->banana sugar delivery ~1/5) - reuses PR #49's evidence verdicts verbatim; all 27 production pairs from its snapshot are covered consistently (21 re-recorded with honest statuses, 6 already consistent) Swift package, tests, SCHEMA.md and REPORT.md follow in the next milestones. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
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 |
Foundation-only SwiftPM package over the reviewed substitution table: restriction-aware ranked lookup that refuses allergen-introducing swaps (compound expansion included), context verdicts (verified/plausible/ unsupported/unknown, unknown never upgraded), deterministic ranking, strict schema validation, and 33 Linux tests including the all-pairs x all-restrictions allergen sweep and byte-identical determinism. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
The bundled recipe data references "mushroom"; the registry carried the mushrooms identity without the singular alias, so app lookups failed closed. Alias fix only; no pair changes. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
flsub-report resolves every bundled recipe ingredient line through the table's alias map, counts verified-swap coverage per restriction, ranks gaps, and prints an evenly spaced 40-pair audited sample with verdicts recomputed by ContextRules. Two runs are byte-identical. SCHEMA.md documents the file format, shared Suite C allergen contract, compound expansion, verdict rules, macro-delta method, source policy, and the regeneration command. REPORT.md is the committed generated output. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
SamGu-NRX
left a comment
There was a problem hiding this comment.
Verdict: BLOCK
Reviewed 0dc3bdf against SPEC section 3.5, the recipe-platform substitution requirements and the PR body. No exact task-row match found.
Blockers:
- data/substitutions/pairs.json:30-50 and Lookup.swift:46-47,85-86. Vegan/vegetarian restrictions omit classes used by their own ingredient registry. shortening-fat-to-lard is sourced/verified and lard carries only animal_product, so it is offered to vegan and vegetarian eaters. anchovy-umami-to-worcestershire likewise passes with fish + animal_product because the diet rules forbid flesh_fish instead. Reconcile diet classification with the actual registry and add independent expected-refusal cases.
- data/substitutions/pairs.json:6764-6783. Generic tamari is declared soy-only and aliased with wheat-free soy sauce, while its note admits labels vary. soy-sauce-umami-to-tamari is consequently verified and offered for gluten-free eaters without product evidence. Separate confirmed wheat-free products from composition-unknown tamari and refuse the latter for affected eaters. A note to read the bottle cannot substitute for the recheck.
Lesser findings:
- Tests/FLSubstitutionsTests/AllergySweepTests.swift:13-23 derives expected safety from the same classes and forbidden sets as the implementation. It cannot detect either taxonomy error above. Add independently specified compound, label-unknown and diet cases, including household unions.
- Tests/FLSubstitutionsTests/RatioScalingTests.swift:58-69 and the table's macro_delta values multiply grams by every ratio, including volume and quarter-cup-per-egg ratios. A volume ratio is not a mass ratio; partial butter-to-applesauce also omits retained fat. Use evidenced densities/component amounts or leave the delta unknown. Macros.sodiumMg is also contrary to the spec's grams-internally contract unless an explicit boundary conversion is established.
- ContextRules.swift:41-47 ignores any failure row when the ratio is sourced, without inspecting why it fails. Encode conditional restrictions separately from actual functional failures so sourced ratio evidence cannot override a role failure.
- The coverage report clearly discloses any-role association rather than actual recipe-role verification. Preserve that limit in consumers. Lookup.swift:17 exposes the primary ratio even for an alternative requested role; store each supported role's actual ratio before offering scaling.
Criteria: 250+ cited pairs met; role/evidence statuses partly met; safe all-eater refusal missing; truthful ratio/nutrition partly met; regenerable coverage and 40-pair sample met; regression tests partly met; scope/no workflow edits met. 3/7 met. Existing iOS CI run 38151147654 passed; the PR reports 33 Linux tests, but these new package checks are not shown in that CI. No local tests, Xcode or simulator run. Citations were inspected as recorded evidence, not blanket clearance of every upstream source.
Pins the FLIngredientFacts API contract and semantics (fail-closed restriction evaluation), the dataset layout with sourced evidence rules, and the audit index for per-PR integration fix lists. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Fail-open audits at each PR head: per-module ingredient lists, absent restriction evaluation, and missing unknown states, with exact FLIngredientFacts / data-conformance integration steps per file. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Fail-open audits at each PR head: claims-based screening with defaults, sim catalogs built without facts, substitution class divergence (tamari/lard/worcestershire), name-only pantry feasibility, and scenario keyword expansion, with exact FLIngredientFacts / data conformance integration steps per file. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
FLIngredientFacts integration fix list — restriction checks fail openAudited at PR head PR #114 — Integration fix list: restriction checks fail openPR head at audit: 1. Where the restriction code sits at this head
2. Fail-open behavior found
3. Exact changes needed (file by file)Goal: composition facts come from one dataset; unknown ⇒ ask the user; restriction semantics shared; the reviewer examples become conformance cases. 3.1
|
Generator produces identities.json (canonical keys, display names, synonyms, USDA FDC ids from the bundled catalog where matched, app local ids for the 50 core ingredients) and facts.json (components with certainty, sourced allergen facts, gluten as a separate property, diet facts, certified-gluten-free tamari product form). Validation enforces evidence on every non-unknown fact, resolvable component keys, unique match names and a path-based cycle check. Corpus coverage enumerates the bundled app data and the seven originals branches programmatically. Draft checkpoint: corpus compounds from the originals compound-allergens tables still to be expanded. 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>
… labels Reviews 5485500522 (BLOCK) and 5485457120 (FIX): a gluten-free label supports the gluten property only — FDA permits processed wheat-derived ingredients in gluten-free-labeled foods, so GF evidence never establishes wheat absence (Q&A General question 6). Certified-GF tamari now keeps wheat unknown; new gluten-free-bread identity models the FDA case (GF label + declared wheat starch: glutenFree allowed, wheat violated); spelt and farro are wheat-species violations; certified-GF oats form carries wheat-free (standard) plus gluten-free (label) evidence. Both READMEs now pin the identities/facts JSON schemas, evidence payload and per-result evidence rules — absence facts (free/allowed) may not use usda/recipe evidence, enforced by the generator validator — and mark deliverables not yet in the tree as PLANNED. Verification commands are all repository-root relative. Forms overrides are validated through the same fact checks. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
- PR-93: recipe-level reject/hold verdict in RecipeRepository (lines 130-198) with a rescue counterexample; safe throwing store loader. - PR-94, PR-101: dispatch on the typed FLEvaluation kind discriminant, exhaustive with never-guard, reason/missing preserved; wrapper-level rejection tests for violated and unknown. - PR-119: re-audited at the real head eb303be (packages/ScenarioJoin, SwapGate, FLDomain Restriction defaults) replacing the invented citations; URL-based store loading. - PR-99/103/105/114/117: conformance commands rooted at the repository root. 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>
Adds the compound ingredients enumerated by the originals-branch compound-allergens tables (teriyaki, hoisin, doenjang, ssamjang, doubanjiang, black-bean-sauce, shrimp-paste, thai-curry-paste, satay, peanut-sauce, mole, aioli, hollandaise, bechamel, fresh-pasta, wonton-wrappers, gyoza-skins, gnocchi, orzo, tempeh, soy-milk, chocolate) and the corpus simple identities (nut, fish, meat, dairy and stock entries), each with sourced facts or honest fail-closed gaps. Ghee, coconut and pine-nuts are promoted to named identities; tahini gains the tahina alias; a generic vinegar part distinguishes distilled from malt. Compound diet facts now propagate from component classes with certainty (variable components demote allowed to unknown); compound allergen facts derive from certain/typical component carriers, explicit unknowns for variable carriers, and free facts only for complete compositions. 193 identities, 261 fact entries, 85/85 corpus names covered; absence facts may not use usda/recipe evidence (validator- enforced). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
usdaFdcId and appLocalIds are integers, usdaRef/usdaNote name the
matched catalog row, and forms are {key, display, synonyms} records
matching the README pin. Fact entries now carry their class. Generator
validator and coverage name-index follow the new form shape.
Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
FLIngredientFactsStore resolves keys, display names, synonyms and preparation-form match names; evaluation gives violated > unknown > allowed, propagates compound components, treats variable components as unknown-when-violating, and keeps wheat and gluten evidence independent. The loader rejects malformed data, unresolved references and absence facts resting on catalog/recipe evidence. All 43 conformance cases pass through the real store (flfacts-conformance). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Both runners re-implement the pinned evaluation semantics from scratch (no shared code with the Swift package), load and strictly validate the dataset, run all 43 shared cases and exit nonzero on any failure. All 43 cases pass in both, matching the Swift store result for result. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
33 XCTests: the real dataset loads; every pinned malformed-data class is rejected (bad JSON, unknown vocab, unresolved component or form, missing evidence fields, absence facts backed by USDA or recipe evidence, duplicate synonyms); all 43 shared cases run through the real store; fail-closed, unknown propagation, certainty levels, union restrictions and the independent wheat/gluten axes are unit-tested, including a synthetic compound that violates purely via a certain component. README status tables now reflect what exists. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Replaces the PLANNED markers on cases.json and the two runners with present-tense status; all three are in the tree and green. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
The TypeScript and Python runners validated evidence ref/note only on fact-level objects and accepted empty strings; a dataset copy with a component evidence 'ref' stripped loaded fine and all 43 cases passed. Both runners now run the same completeness check the Swift loader enforces — known kind, non-blank ref and note on every evidence object, fact-level and component-level. The Swift loader additionally rejects blank (whitespace) ref/note, which typed decoding alone let through. Adds negative_checks.sh, which replays six malformed-data mutations against all three implementations (bun runner, Python runner and the flfacts-conformance binary) and exits nonzero if any of them accepts a mutation, plus four new Swift loader tests (37 total). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
Merges overhaul/ingredient-facts (b0deecc) into overhaul/substitution-table per the PR #114 integration fix list: the substitution table's class lists and the shared ingredient-facts dataset are two divergent lists describing the same ingredients. Composition facts now come from one canonical dataset; the next commits rewire the restriction recheck through store evaluation so unknown composition fails closed (ask the user) instead of passing as safe. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
SamGu-NRX
left a comment
There was a problem hiding this comment.
Verdict: BLOCK
Reviewed 7b85bec against SPEC 3.5, the substitution requirements and the PR body. No exact task-row match, so task_id remains null. Substitution source, table, report and tests are unchanged since 0dc3bdf. The inherited ingredient-facts files match the already reviewed #127 head b0deecc; merging them does not connect them to FLSubstitutions.
Blockers:
data/substitutions/pairs.json:30-50andpackages/FLSubstitutions/Sources/FLSubstitutions/Lookup.swift:46-47,85-86still use incompatible diet classes. Lard carries only animal_product, which neither vegan nor vegetarian forbids, so shortening-fat-to-lard is offered as verified. Worcestershire carries fish and animal_product, while those diets forbid flesh_fish, so anchovy-umami-to-worcestershire is offered as plausible. Reconcile the diet rules with the actual ingredient vocabulary or use sourced canonical facts, and add independently specified refusal cases.data/substitutions/pairs.json:6764-6783still gives generic tamari only the soy class and aliases it with wheat-free soy sauce, while its own note says labels vary. The lookup consequently offers soy-sauce-umami-to-tamari to gluten-free eaters without product evidence. Separate confirmed products from generic composition-unknown tamari and refuse unresolved checks. A read-the-bottle note does not enforce the recheck.packages/FLSubstitutions/Package.swift:10has no ingredient-facts dependency, and Lookup still reads only the old class arrays.
Lesser findings:
AllergySweepTests.swift:13-23derives expected refusals from the same class arrays and forbidden sets used by lookup. It cannot catch either taxonomy/evidence defect above. Add household-union, compound and unknown-product cases with independent expectations.RatioScalingTests.swift:58-69and table macro deltas still apply ratio values to grams for volume and kitchen-unit ratios. Use evidenced densities/component amounts or leave the delta unknown. Supported alternative roles also expose the primary ratio throughLookup.swift:17.ContextRules.swift:41-47still disregards a fails row whenever its ratio is sourced, without distinguishing an eater condition from a functional failure. Source evidence for a ratio cannot erase a failed role.- The inherited #127 dataset remains BLOCK for incomplete composition clearance and unsupported absence facts. It needs its own fixes before adoption; this review does not clear that dataset.
Task criteria, 3/7 met:
- 250+ cited pairs: met. The 279-pair table is unchanged.
- Role/evidence statuses: partly met. Functional failure handling remains incorrect.
- Safe all-eater refusal: missing. Known diet and unknown-product bypasses remain.
- Ratio/nutrition: partly met. Unit and alternative-role problems remain.
- Regenerable coverage and 40-pair sample: met in the unchanged artifacts, with any-role coverage limits preserved.
- Regression tests: partly met. The sweep repeats the implementation's assumptions.
- Scope/no workflow edits: met for substitution commits. This update inherits the facts and PR-writing assets.
Current-head app build-and-test and dead-code-advisory checks passed in run 38193326657. They do not run this standalone package. The body still names 0dc3bdf and its 33-test Linux proof; no current-head package execution is shown. No local tests ran.
Integrates the canonical FLIngredientFacts store as the composition referee for substitution rechecks (fix list, docs/audits/ingredient-facts/PR-114.md): - every ingredient record carries an explicit facts_key to the canonical identity (exact-name mapping: 123 matched, 258 fail closed as composition-unknown - never fuzzy); - rechecks evaluate the store: allowed / violated(reason names the offending component) / unknown - unknown fails closed as ask-the-user and is never offered; RestrictionSet keeps name validation, its forbids now live in the shared FLRestrictions vocabulary, and the both-sides recheck stays; - the both-sides recheck also runs when no restrictions are supplied (it settles allowed), so determinism serializations are unchanged; - macro deltas exist only on mass-ratio rows (a volume or quarter-cup-per-egg ratio is not a mass ratio); 148 rows were nulled with explicit reasons, 34 mass-basis rows keep their deltas; - conditional failure rows (5) are separate from functional failures: they refuse for named restrictions without degrading the verdict; - supported_also roles expose only their own recorded ratio; scaling never leaks the primary ratio into an alternative role; - taxonomy reconciled with the store: lard += pork flesh, fish_sauce/worcestershire += fish flesh, oyster_sauce += shellfish flesh; tamari loses the unevidenced "wheat-free" alias (soy-certain, gluten-unknown; the certified gluten-free form is a distinct product-form entry with label evidence); coconut milk labels align with the store's treeNuts: free composition verdict; - tests: reviewer examples as first-class cases (lard and worcestershire refused for vegetarian/vegan; generic tamari asks the user for gluten-free; missing registry entries ask the user, never offered; restriction unions screen both axes; conditional seitan refusal), a store-violated refusal sweep, role-ratio no-leak, and mass-basis delta reproduction (43 tests, 0 failures). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
… docs The coverage report now loads the canonical facts dataset (sibling data/ingredient-facts by default, --facts to override) and reports a composition-status section: 124 ingredient records resolve in the canonical store, 257 are composition-unknown (no canonical record; rechecks ask the user under restrictions). The formerly invisible 211 empty-class-list records now count as their own class and shrink visibly as records gain reviewed facts. Restricted coverage in REPORT.md honestly collapses where substitutes lack canonical composition records (dairy-free 265 -> 90 covered lines, gluten-free 299 -> 24, soy-free 278 -> 0): the previous numbers were an artifact of rechecks never screening compositions. Unrestricted coverage is unchanged at 299/781. SCHEMA.md documents schema 1.1.0: facts_key (exact-name canonical identity, null fails closed), restriction fl mappings, fails[] kind (functional vs conditional with named restrictions), role-specific supported_also ratios (an alternative role never inherits the primary ratio), free-form ratio bases with the mass-statement scaling contract, the canonical-recheck semantics (allowed / violated / unknown -> ask-the-user), the taxonomy alignments (lard, fish sauce, worcestershire, oyster sauce, coconut milk labels), the tamari product-form rule, and the mass-basis-only macro-delta method. The report runs are byte-identical (verified twice). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
|
@SamGu-NRX the two BLOCK review blockers and all lesser findings are addressed at 00c36b4 (43 + 37 tests green in-sandbox, reviewer examples as first-class cases in Tests/FLSubstitutionsTests/ReviewerExamplesTests.swift, composition-status report showing 124 reviewed / 257 composition-unknown). Proof and Handoff are updated in the body. Re-review requested. |
SamGu-NRX
left a comment
There was a problem hiding this comment.
Verdict: FIX
Reviewed 00c36b4 against SPEC 3.1/3.5 and the substitution requirements. No exact task row matches. All 14 changed files were inspected, including the table changes. Canonical rechecks now refuse lard and Worcestershire for vegetarian/vegan eaters and generic tamari for gluten-free eaters. Named regression tests cover those cases. Functional failure rows now override sourced ratios, and non-mass ratios no longer carry nutrition deltas.
Blockers:
packages/FLSubstitutions/Sources/FLSubstitutions/Lookup.swift:20,31-42still exposes the primary role's macro delta for an alternative role whose ratio is unknown.egg-binder-to-mashed-bananarequested as liquid has no alternative ratio and refuses gram scaling, but its candidate still returns the binder's nutrition delta. Return unknown nutrition until the requested role's amount is evidenced, or recompute from that amount. The new alternative-role tests check ratios, not this nutrition path.packages/FLSubstitutions/Sources/FLSubstitutions/Restrictions.swift:142-149andLookup.swift:105-108allow a needs-confirmation candidate into the usableswapsresult when only the original composition is unknown. For example, buttermilk-to-whole-milk under vegetarian returns a verified swap with unknown original facts. The API promises safe, usable results and the schema says unknown rechecks are never offered. Require an explicit confirmation path before treating these candidates as usable, while allowing a known original violation to be removed by a safe replacement.
Lesser findings:
packages/FLSubstitutions/Sources/flsub-report/main.swift:196-204labels any resolved identity as composition reviewed. A resolved generic tamari or incomplete chicken stock is counted there despite unresolved composition. Separate identity resolution from complete composition and restriction-specific outcomes. SCHEMA also says 123 matched while REPORT says 124, because display-name fallback resolves another entry.ContextRules.swift:43,50checks only the first failure row for a role. A conditional row before a functional row hides the latter; the loader permits both. Search for any functional failure and validate duplicate/conflicting role rows.- The inherited ingredient-facts dataset remains subject to #127's unsupported absence findings. This review clears the named substitution counterexamples, not those upstream facts.
- The PR body still reports 33 tests, old restricted coverage and final head 0dc3bdf. REPORT now shows substantially lower restricted coverage, including zero soy-free coverage. Update Proof/Handoff to this revision.
Task criteria, 5/7 met:
- 250+ cited pairs: met, 279 pairs and 23 registered sources.
- Role/evidence statuses: met for the shipped functional/conditional rows; malformed duplicate rows need validation.
- Safe all-eater refusal: partly met, canonical substitute checks and restriction unions work, but confirmation is not enforced for unknown originals.
- Ratio/nutrition: partly met, non-mass deltas and alternative scaling are corrected; alternative-role nutrition still leaks.
- Regenerable coverage and 40-pair sample: met, generator and updated report inspected; composition label needs correction.
- Regression tests: met, named blocker cases, union checks and non-mass/alternative-ratio checks added.
- Scope/no workflow edits: met, this delta stays within substitution data/package paths.
REST build-and-test is in progress in run 38197653995 at this head. The app workflow does not run this standalone package. No current-head package proof appears in the body. Source and CI review only; no local tests ran.
PR #114 review round 2, fix 1. A candidate whose recheck needs confirmation — composition unknown on either side, per the canonical facts store's fail-closed evaluate — used to land in the usable swaps result when only the ORIGINAL ingredient's side was unknown: buttermilk to whole milk under vegetarian came back as a verified swap on the strength of the reviewed replacement alone. The lookup now separates verdicts into three buckets: usable swaps (no confirmation requirement, no refusal, evidence verdict), needs-confirmation questions (surfaced, never usable), and the refused remainder (settled violations, conditional refusals, non-evidence verdicts). swaps(for:role:restrictions:) returns exactly the usable bucket, and verifiedSwaps excludes needs-confirmation candidates so the coverage report cannot count an unconfirmed composition as verified evidence — REPORT.md is regenerated by flsub-report accordingly. The preserved property holds: a swap whose ORIGINAL is a settled violation and whose replacement is known-safe stays usable — removing the violation is the point of the swap (butter to olive oil under vegan, now pinned first-class). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
…-role evidence SwapCandidate.macroDelta returned the primary role's recorded delta for every requested role. A swap requested FOR an alternative role now computes its delta from THAT role's own evidenced mass ratio (the SCHEMA.md macro-delta method, mass statements only - review finding d), or reports unknown (nil) until the role's amount has evidence. The reviewer's pin - egg as a binder to mashed banana, requested as a liquid - reads as unknown nutrition, never the binder delta reskinned. Primary-role requests keep the recorded delta unchanged. Tests on the nutrition path (asserting the returned delta, not the stored ratio): the reviewer's liquid case is unknown on the shipped table; an alternative role with an evidenced per-role mass ratio computes from that ratio on a synthetic table (distinct from the primary's, math shown); primary-role deltas are unchanged across the whole table; non-mass and unsupported alternative ratios stay unknown. All 49 pre-existing tests pass unmodified. PR #114 review round 2, fix 2. Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
SamGu-NRX
left a comment
There was a problem hiding this comment.
Verdict: FIX
Reviewed a6cc83e5fb7fc3ee71b34412ca362b750807fa0c against SPEC 3.1/3.5 and the established substitution scope, task_id null. The head moved before posting the c7784c0 draft, so this review includes both commits and all six changed files. Unknown composition is now separated from usable swaps and verified coverage. Alternative-role nutrition now uses that role's mass ratio or returns nil; the banana-as-liquid counterexample is fixed, with independent numeric fixture assertions.
Blockers:
packages/FLSubstitutions/Sources/FLSubstitutions/ContextRules.swift:37-38,54-56uses the alternative role's functionstatusas its ratio evidence. The shippedegg-binder-to-mashed-bananaliquid row hasstatus: sourcedbut no ratio, so it still getsverifiedand appears in unrestricted verified coverage, despite unknown amount and the now-correct nil nutrition. The same holds for applesauce-as-liquid and seven other shipped alternative rows. Derive ratio status fromalso.ratio, using unknown when absent, and assess function status separately. A sourced role association cannot establish an amount.ContextRules.swift:43,50takes only the first failure row matching a role. A conditional row followed by a functional failure for that role hides the functional refusal and can return verified.SubstitutionTable.swiftchecks IDs and conditional restriction names, but accepts those duplicate role rows. Search all applicable functional failures or reject the conflict at load time.
Lesser findings:
Sources/flsub-report/main.swift:196-204still labels resolved identities composition reviewed, including incomplete facts. Distinguish identity resolution from restriction/composition evidence.- The inherited ingredient-facts dataset retains #127's unsupported-absence findings. The confirmation repair enforces unknown results that reach it; it does not correct false allowed results upstream.
- The current body has Proof for c7784c0's 49 tests but still ends with the older 00c36b4 Handoff SHA. This new nutrition commit needs current Proof/Handoff.
Task criteria, 6/7 met: 279 cited pairs met; role/evidence verdicts partly met; all-eater confirmation filtering met within the canonical store's verdicts; role-aware ratio/nutrition output met; regenerable report and sample met; regression coverage met; ownership met. No recipe text or workflow changes in these commits.
Exact-head CI run 38202908480 is still building. App CI does not run this package. Static source and data review only; no local tests, Xcode or simulator.
…quency Operator ruling (PR #114 review round 2): the 257 composition-unknown entries need no reviewed facts before merge (fail-closed is the contract), and reviewing them is coverage work owned by FLIngredientFacts (PR #127). This gives that task its starting order: flsub-report gains an optional --originals flag and ranks the unknown entries by recipe-line frequency across the bundled recipes and the originals corpus, read from origin/overhaul/original-recipes via git show (the branch is never checked out or pushed). Matching rule (stated in the report): a corpus ingredient counts toward a table entry when the table's own resolution matches it exactly, case-insensitively, on key, display, or a recorded alias, plus the app data's underscore-for-space fallback; occurrences are recipe ingredient lines (required + optional), so every count is a lower bound. All 50 bundled app ingredients resolve to reviewed facts, so the ranking is driven by the originals corpus (401 recipes, 2829 ingredient lines, 2165 resolved to a table entry); the head of the list is salt (91), crushed tomatoes (79), vegetable oil (43). Existing report sections stay byte-stable; the header regenerate command gains the fetch/show and --originals steps. Suite 54/54; two runs byte-identical (cmp). Co-authored-by: obvious-autobuild[bot] <262744130+obvious-autobuild[bot]@users.noreply.github.com>
|
@SamGu-NRX round 2 is code-complete at 4b58954 (append-only: c7784c0 → a6cc83e → 4b58954 on top of the reviewed 00c36b4). Point by point: Fix 1 — needs-confirmation never counts as usable (c7784c0). Fix 2 — alternative-role nutrition is unknown without per-role evidence (a6cc83e). Product ruling honored. The 257 composition-unknown entries are not blocked on reviewed facts: fail-closed stays the contract (an unknown entry is never offered as a usable swap and shows as a check to the user), and no second fact base was built on the substitution table. For FLIngredientFacts (fl-root-ingredient-facts-f1, PR #127) this head delivers the review order: REPORT.md's new tool-generated section "Composition-unknown entries seen most often" — the top 30 unknown entries by recipe-line frequency across the bundled recipes ( Sandbox proof at 4b58954 (Linux, SwiftPM, no Xcode; no CI claims): Two independent On the score: the independent reviewer's last published read was FIX 5/7 at 00c36b4 — nothing is claimed at this head; c7784c0, a6cc83e and 4b58954 are newer and await its re-read. Re-review requested at 4b58954. The body's Handoff cites the same head; Proof records the replay command and digest. |
SamGu-NRX
left a comment
There was a problem hiding this comment.
Verdict: FIX
Reviewed 4b5895409cb1c990a5e8251f490fe80ccd35c295 against SPEC 3.1/3.5 and the established substitution scope, task_id null. The handoff explicitly requests re-review at this head. Since a6cc83e, only REPORT.md and the report generator changed; verdict, lookup and nutrition source and tests are unchanged.
Blockers:
packages/FLSubstitutions/Sources/FLSubstitutions/ContextRules.swift:37-38,54-56still uses an alternative role's function status as ratio evidence. The shipped egg-binder-to-mashed-banana liquid row is sourced but has no ratio, yet receives verified. Nine shipped sourced alternative-role rows lack a ratio. The nutrition fix correctly returns nil, but verified lookup and coverage still overstate the evidence. Derive ratio status from that role's ratio, using unknown when absent, and assess function evidence separately.ContextRules.swift:43,50still checks only the first failure row for a role. A conditional row before a functional failure hides the functional refusal.SubstitutionTable.swift:145-162accepts that conflict. Check every applicable functional failure or reject duplicate conflicting role rows when loading.
Lesser findings:
packages/FLSubstitutions/Sources/flsub-report/main.swift:229-247,314,363-380calls identity resolution composition review.FLIngredientFactsStore.resolveresolves names without evaluating restriction evidence. Existing identities with incomplete composition therefore disappear from the new unknown-composition ranking. Label this list unresolved identities, or derive unknown composition from facts and the relevant restrictions.main.swift:366-368anddata/substitutions/REPORT.md:137-139say unknown composition is never offered as a verified swap and always shows a check.Restrictions.swift:199-202explicitly skips composition checks when restrictions is empty;Lookup.swift:270-287can return verified in that case. Qualify the report's statement for restricted eaters rather than presenting it as an unconditional guarantee.REPORT.md:7-8regenerates against a moving originals branch without recording its input SHA or digest. Pin that input in the report so later corpus updates do not silently change this evidence.- The inherited canonical dataset's unsupported-absence findings remain tracked in #127. This report update does not correct upstream false allowed results.
The new ranking's counts match static parsing of originals at 607ef996e685ed250c0c0036690736587fddf728: 401 recipes, 2829 named lines, 2165 table matches, 257 unresolved table entries, 50 with occurrences, and all 30 displayed rows. The committed report SHA-256 matches Proof. This verifies the ranking for those inputs, not the composition-review label or executable determinism claim. Current Proof/Handoff now identify 4b58954; the previous stale-head finding is resolved.
Task criteria, 6/7 met: cited 279-pair table met; role/evidence verdicts partly met; all-eater confirmation filtering met within the canonical store's verdicts; role-aware ratio/nutrition output met; generated report and sample met with the qualifications above; regression coverage met; ownership met. No workflow changes or recipe expression added in this delta.
REST CI run 38203749537 reports build-and-test success and dead-code-advisory in progress. App CI does not run FLSubstitutions. The 54-test Linux run is producer evidence. Static source/data inspection only; no local tests, Xcode or simulator.
Why
SPEC overhaul-20261011 §3.5 requires substitutions to come from a reviewed, context-specific table — ingredient pair, recipe context, quality note, macro delta, allergen check — with LeChef reasoning beyond it only when empty and saying so, and §3.1 requires every substitution to recheck all eaters' allergens including compound ingredients (soy sauce contains wheat, pesto contains nuts), describing the difference honestly and never promising "no change". Current production behaviour is a hardcoded 27-pair map in
SubstitutionService.buildMap(). This PR ships the reviewed table, the package that serves it safely, and an auditable coverage report.This round resolves the BLOCK review at
0dc3bdf(both blockers, all lesser findings) and implements the committed fix list (docs/audits/ingredient-facts/PR-114.mdonoverhaul/ingredient-facts): the canonical ingredient-facts source is integrated fail-closed.What changed
data/substitutions/pairs.json— the reviewed table: 279 cited pairs across all 11 functional roles, a 381-ingredient registry with USDA SR Legacy identities, 23 registry sources, 13 restriction definitions. Per pair: functional role, ratio with evidence status and basis, preparation changes, one plain-language difference sentence (never "no change"), allergen/diet classes for both sides with compound expansion, and the roles where the swap fails. All 27 production pairs from PR feat(evaluation): substitution evidence harness — 27 pairs, 227 recipe contexts, Swift replay #49's frozen snapshot are represented; conflicts are recorded, not silently resolved.Review-fix round (BLOCK review + fix list)
7b85becis a history-preserving merge ofoverhaul/ingredient-facts(choosing the merge over vendoring so the package and dataset history stays joined and attributable).packages/FLIngredientFacts+data/ingredient-factsare now the canonical composition referee; the table's class lists are evidence labels kept in step with it.facts_key— exact-name mapping against the store's identities, never fuzzy: 123 of 381 records map; 258 map to no canonical record and fail closed.store.evaluate(ingredient:restrictions:)returnsallowed/violated(reason)(the reason names the offending component) /unknown. Unknown composition is ask-the-user and never offered — a missing record or an unreviewed one can no longer pass every restriction check (the old fail-openingredients[key]?.classes ?? []pattern is gone).RestrictionSetkeeps its name validation; its forbids are now the shared-vocabularyFLRestrictions; the both-sides recheck stays (a swap is offered only when the substitute screens clean).lardcarriesmeat_whole_cut_pork,fish_sauceandworcestershirecarryflesh_fish,oyster_saucecarriesflesh_shellfish— the classes vegetarian/vegan forbids actually screen. The fix list's second branch was chosen over addinganimal_productto the forbids lists because the canonical diet facts mark butter, eggs and honey vegetarian-allowed; the marker stays an evidence label, not a forbid.tamari-certified-gluten-freeindata/ingredient-facts) carrying label evidence, and it evaluates allowed.ReviewerExamplesTestspins the review's examples as first-class named cases (see Proof). The sweep gained an independent store-outcome sweep: wherever the store settles a violation for a substitute, the candidate must be refused across all pairs × restrictions — it does not derive expectations from the same class lists the implementation consumes. Household unions screen both axes in oneFLRestrictions.weight/weight-cookedrows: 148 deltas were nulled with explicitmacro_delta_null_reason(dimensionally invalid multiplication; partial butter-to-applesauce also omits the retained-fat component), 34 mass-basis deltas remain and are recomputed by the tests. Sodium stays in mg inside the grams-internally contract; no boundary conversion was invented. Alternative roles get role-aware scaling: asupported_alsorole exposes only its own recorded ratio (nil today — no such row has an evidenced ratio), and scaling refuses to run off the primary ratio.kind: "conditional"with namedrestrictions(e.g. seitan as a protein: refused for gluten-free eaters, offered otherwise); a sourced ratio can no longer override a role failure, and a conditional refusal does not degrade the swap's own verdict.data/ingredient-factsby default,--factsto override) with a new composition-status section: the formerly invisible 211 empty-class-list records now count as their own class — 124 records resolve in the canonical store, 257 are composition-unknown — so the unknown bulk shrinks visibly as records gain reviewed facts.tree_nutlabel per the store's composition verdict (closed composition of coconut and water — tree-nut free; raw coconut stays unknown in the store and fails closed).SCHEMA.mddocuments schema 1.1.0 (facts_key,flmappings,fails[].kind/restrictions, role-specificsupported_alsoratios, the mass-statement scaling contract, canonical-recheck semantics, the tamari product-form rule, the taxonomy alignments).Boundaries: only
data/substitutions/andpackages/FLSubstitutions/were touched this round, plus the merge commit.overhaul/ingredient-factswas consumed read-only (merged, never pushed to). No workflows, app code, settings, or other PRs touched. iOS-native behaviour is UNVERIFIED-NATIVE (no Xcode/macOS in this environment).Tradeoffs
verified(sourced function and sourced ratio); the rest rank accordingly — quality over count, unknown stays unknown.gluten; wheat-specific medical notes live inallergen_note. Diet and FSIS meat/flesh classes are documented extensions that never enter the shared nine.swift build/swift testpass on Linux; anything iOS-native is unverified here and labelled UNVERIFIED-NATIVE.Proof
Environment: Linux sandbox, SwiftPM, no Xcode. No CI claims — CI run ids are referenced, not asserted. Fix-1 commands ran at the pushed head
c7784c0; fix-2 commands ata6cc83e.Review round 2, fix 2 — alternative-role nutrition is unknown without per-role evidence (
packages/FLSubstitutions):At the fix-2 head
a6cc83e(append-only on top ofc7784c0):54 = the fix-1 49 (all kept green, unmodified) + 5 new
AlternativeRoleNutritionTests.Red-first proof at
c7784c0(the tests alone, before the source fix):4 of the 5 fail with the leak reproduced on real data: the egg (binder) → mashed-banana candidate requested as LIQUID returns the binder's recorded delta (
kcal -18, protein -5.6, carbs 13.3, fat -4.6, fiber 1.6, sugar 0.0, sodium -70.4), as do its leavening and sweetener requests, and the synthetic evidenced-liquid case returns the primary's delta (kcal -40, protein -3.6, …) instead of computing from the liquid ratio.The fix:
SwapCandidate.macroDeltais role-aware. For the primary role it returns the pair's recorded delta unchanged; for an alternative role it computes the SCHEMA.md macro-delta method from THAT role's own evidenced ratio — mass statements only (review finding d: a volume or quarter-cup-per-egg ratio is never a mass multiplier),unknown/unsupportedamounts are not computation inputs — and reports unknown (nil) until the role's amount has evidence. The reviewer's pin — egg as a binder to mashed banana, requested as a liquid — reads as unknown nutrition, never the binder delta reskinned. No result-type change was needed:Macros?nil is the unknown representation.The new cases (asserting the RETURNED delta on the nutrition path, not merely a stored ratio):
testEggBinderToMashedBananaRequestedAsLiquidHasUnknownNutrition— the reviewer's exact case: liquid, leavening (functionally failed), and sweetener requests are all unknown on the shipped table.testAlternativeRoleWithEvidencedRatioComputesFromThatRatio— the shipped table has no evidenced alternative-role ratio (everysupported_alsorow is ratio-less), so a synthetic schema-1.1.0 fixture carries a sourced liquid mass ratio (0.5 per gram, 50 g typical): the returned delta is computed from THAT ratio (kcal -75, protein -4.4, carbs 2.5, fat -4.7, fiber 0.0, sugar 2.5, sodium -42.5), distinct from the primary's recorded-40, -3.6, 9.5, -4.3, 0.7, 7.4, -32; the math is shown in the test.testPrimaryRoleRequestsKeepTheRecordedDeltaAcrossTheWholeTable— primary-role requests returnpair.macroDeltaunchanged for every pair in the shipped table.testAlternativeRoleWithNonMassRatioHasUnknownNutrition— review finding d on the alternative path: a quarter-cup-per-egg ratio never becomes nutrition arithmetic.testAlternativeRoleWithUnsupportedAmountHasUnknownNutrition— an amount recordedunsupportedis not evidence to compute from.No legacy test encoded the primary-delta leak: the pre-existing macro-delta tests exercised
Pair.macroDeltadirectly, and no test asserted a candidate's alternative-role delta — so all 49 stayed green unmodified; nothing needed re-pointing to the reviewer's contract.Coverage report unchanged — regenerated at this head through the tool and
diffed byte-identical against the committeddata/substitutions/REPORT.md; per the only-if-output-changes rule nothing was regenerated into the tree. Environment note: one in-tree release build died on an llbuild assertion (BuildEngine.cpp:281) mid-swift run -c release; rebuilt in a clean build path and the tool then ran to exit 0 with identical output (deterministic by construction).Review round 2, fix 1 — needs-confirmation never counts as usable (
packages/FLSubstitutions):49 = the prior 43 (all kept green) + 6 new
NeedsConfirmationSeparationTests.The reviewer's exact case, first-class —
testButtermilkToWholeMilkUnderVegetarianNeedsConfirmationAndIsNeverUsable:buttermilk-liquid-to-whole-milkundervegetarianhasneedsConfirmation == true(original sideaskUser: buttermilk has no canonical facts record), verdictverified, substitute outcomeallowed— and is demonstrably no longer classified as verified/usable: absent fromswaps(...)and fromclassifiedSwaps(...).usable, landing in.needsConfirmation.The preserved property, first-class —
testKnownViolationOriginalWithKnownSafeSubstituteStaysUsable:butter-fat-to-olive-oilunderveganhas originalviolated("'Butter' is not compatible with the vegan diet"), substituteallowed, removesdairy+animal_product, and stays usable/removing; the needs-confirmation bucket does not swallow it.The mechanics (same commit):
Lookup.classifiedSwapsseparates every candidate into usable / needs-confirmation / refused — disjoint, complete, each bucket in the candidates' rank order (asserted across the deterministic lookup battery × restriction batteries × all roles, together withswaps == usable);swapsreturns exactly the usable bucket;verifiedSwapsexcludes needs-confirmation so coverage reporting cannot count unconfirmed composition as verified evidence.Coverage report regenerated by the tool, not hand-edited (the tool's output changed at this head, per the fix-list rule):
The restricted-coverage drop is the honest post-fix number, not a regression: the store fails closed for reviewed ingredients whose records lack a fact for the axis — e.g.
evaluate(Butter, gluten-free)=unknown("diet 'glutenFree' fact for 'butter' is missing or unknown"), same for olive oil/milk under egg-free — so those swaps are needs-confirmation questions, never verified coverage. Diet facts for dairy-free/vegan/vegetarian are broadly recorded, so those rows stay meaningful. Unrestricted coverage is unchanged.Round 1 proof (head
00c36b4) — kept for the record:The review's round-1 examples as named, passing tests (from
ReviewerExamplesTests):testLardIsRefusedForVegetarianAndVeganEaters— store-settled violation, lard never offered, unrestricted verdict untouched.testWorcestershireIsRefusedForVegetarianAndVeganEaters— same, via theflesh_fishreconciliation.testGenericTamariIsAskTheUserForGlutenFreeEaters—askUserreasons name tamari; never offered; the composition-unknown note stays.testCertifiedGlutenFreeTamariIsADistinctProductFormWithLabelEvidence— generic tamariunknown, product formallowedon label evidence.testSubstituteMissingFromTheFactsRegistryYieldsAskTheUserAndIsNeverOffered— exercised against the 257 unmapped records.testTwoEatersRestrictionsUnionScreenBothAxes— the milk + peanut union folds into oneFLRestrictions(diet + allergen axes); a dairy/gluten union refuses both axes in one lookup.testSeitanAsProteinIsRefusedOnlyForGlutenFreeEaters— conditional refusal, verdict not degraded.testAlternativeRolesNeverScaleWithThePrimaryRatio— role-aware ratios, no primary-ratio leak (57supported_alsopairs exercised).Coverage report regenerated byte-identical (run twice,
cmp) at that head, with the composition-status section: 124 ingredient records resolve in the canonical store; 257 composition-unknown; verified-swap coverage 299/781 lines (38.3%) unrestricted; restricted coverage as of THAT head (gluten-free 24, dairy-free 90, soy-free 0) was still inflated by the fix-1 leak — see the fix-1 table above for the honest numbers.Attribution: every commit
a5402d9,61551fb,e4a7aa3,0dc3bdf,7b85bec(merge),e12a032,00c36b4,c7784c0,a6cc83eis authored bySam Gu <127461594+SamGu-NRX@users.noreply.github.com>with theCo-authored-by: obvious-autobuild[bot]trailer; authorship verified on the pushed remote head before each push (git ls-remoteverified at each push; current remote heada6cc83e5fb7fc3ee71b34412ca362b750807fa0c). Append-only: no rebase, amend, or force-push.Wrap-up — top-30 composition-unknown list for PR #127 (head
4b58954):Per the operator ruling — the 257 composition-unknown entries need no reviewed facts before merge; fail-closed is the contract (an unknown entry is never offered as a usable swap and shows as a check to the user), and reviewing them is coverage work owned by FLIngredientFacts (task fl-root-ingredient-facts-f1, PR #127) —
flsub-reportgained an optional--originalsflag and REPORT.md a new generated section, "Composition-unknown entries seen most often": the top 30 unknown table entries ranked by recipe-line frequency across (1) the bundled recipes (apps/ios/Resources/data.json) and (2) the originals corpus (data/recipes/originals/originals.jsonlonorigin/overhaul/original-recipes, materialized read-only withgit show— the branch is never checked out or pushed).Matching rule (stated in the report itself): a corpus ingredient counts toward a table entry when the table's own resolution matches it — an exact, case-insensitive match on the entry's key, display name, or a recorded alias, plus the app data's underscore-for-space fallback for snake_case ids; occurrences are recipe ingredient lines (required + optional), so every count is a lower bound. Corpus names that match no table entry, or a composition-reviewed entry, are not listed. All 50 bundled app ingredients resolve to reviewed facts, so every Bundled count in the section is 0 and the ranking is driven by the originals corpus (401 recipes, 2829 ingredient lines, 2165 resolved to a table entry); the head of the list: salt 91, crushed tomatoes 79, vegetable oil 43.
Replay — the header's documented command reproduces the committed REPORT.md byte-for-byte at the pushed tree
4b58954:Determinism and suite at
4b58954:Two independent
flsub-reportruns were byte-identical (cmp); all pre-existing report lines are byte-stable against thea6cc83eoutput except the header's regenerate command, which gains the fetch/show and--originalssteps. Attribution: commit4b58954authored bySam Gu <127461594+SamGu-NRX@users.noreply.github.com>with theCo-authored-by: obvious-autobuild[bot]trailer (verified before push; remote head verified viagit ls-remote).Handoff
Done
c7784c0:Lookup.classifiedSwapspartitions every candidate into usable / needs-confirmation / refused — disjoint, complete, each bucket in rank order (asserted across the deterministic lookup battery × restriction batteries × all roles, withswaps == usable);swapsreturns exactly the usable bucket andverifiedSwapsexcludes needs-confirmation so coverage cannot count unconfirmed composition as verified;buttermilk-liquid-to-whole-milkundervegetarianis pinned needs-confirmation; the preserved property —butter-fat-to-olive-oilundervegan(original violated, substitute allowed) stays usable and still removesdairy+animal_product. Fix 2 ata6cc83e:SwapCandidate.macroDeltais role-aware — primary roles return the recorded delta unchanged; alternative roles compute from that role's own evidenced mass ratio (volume/quarter-cup-per-egg ratios andunsupportedamounts are never computation inputs) or read unknown; the reviewer's pin (egg binder → mashed banana requested as liquid) reads unknown, never the binder delta reskinned; 5 new nutrition-path tests, red-first atc7784c0(5 tests, 15 failures before the source fix).4b58954: REPORT.md's new tool-generated section "Composition-unknown entries seen most often" — the top 30 composition-unknown table entries by recipe-line frequency across the bundled recipes and the originals corpus, matching rule stated in the report; replay command and output digest in Proof. The list is handed to FLIngredientFacts (PR FLIngredientFacts: canonical ingredient facts with fail-closed restriction evaluation #127) as the review order for the 257 unknowns.4b58954:swift test --package-path packages/FLSubstitutions→ Executed 54 tests, with 0 failures (Linux sandbox, no Xcode; no CI claims).Not done / limits
4b58954is the remaining gate. The independent reviewer's last published read was FIX 5/7 at00c36b4;c7784c0,a6cc83eand4b58954are newer and await that re-read.SubstitutionService→FLSubstitutions) is out of this task's file scope and unstarted.How to continue
4b58954— the round-2 point-by-point is in the re-review comment; the named round-1 cases remainTests/FLSubstitutionsTests/ReviewerExamplesTests.swift.packages/FLSubstitutions/+data/substitutions/; the invariants to hold areSCHEMA.md§"Canonical composition rechecks" (fail closed, both-sides recheck, conditional≠functional, role-specific ratios, mass-basis deltas).SubstitutionServicethrough the table (LeChef reasons beyond it only when empty, saying so, per §3.5).Final head SHA:
4b5895409cb1c990a5e8251f490fe80ccd35c295🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact