Mirabelly
Home Pricing Get started

What Mirabelly’s safety net covers — and what it doesn’t

This is the one page that says, plainly, exactly what the allergen and restriction safety net does and does not do in v1. If you are deciding whether to trust the weekly list for someone with a serious restriction, read this first.

We would rather under-promise here than have you discover a gap the hard way. Everything below is the honest boundary — where the net holds, where it stops, and the places we chose to document a limit rather than pretend it away.

The advisory disclaimer that ships on every list says it this way:

These suggestions avoid known allergens and restrictions on a best-effort basis using curated ingredient data. They are NOT medically guaranteed safe. Coverage is incomplete: items we have not yet reviewed are excluded out of caution rather than included. Always verify labels yourself, especially for severe allergies.

What v1 covers

The safety net is written against a fixed matrix of household shapes. That matrix is not a marketing list — it is the literal parametrization the test suite runs, and it is the normative v1 boundary. It lives in tests/fixtures/safety_archetypes.py as the ARCHETYPES tuple (28 rows). Each row id below is stable and cited verbatim; if a shape is not on this list, the suite does not prove the net holds for it.

  • The Big-9 allergens, one hard restriction each: A-milk, A-egg, A-fish, A-shellfish, A-tree_nut, A-peanut, A-wheat, A-soy, A-sesame. These are the FDA Big-9, household-wide and derivation-aware (whey inherits milk).
  • The three diets, whole-household and mixed: D-vegetarian-all, D-vegan-all, D-paleo-all, D-vegan-mixed, D-paleo-mixed, D-vegetarian-solo. Diets are per-person.
  • Household sizes: S-1 (single), S-4 (two adults + two kids), S-6g (six including a partially-present guest).
  • Special populations: P-pregnancy, P-infant, P-elderly.
  • Cross-cutting combinations: X-vegan-treenut-infant, X-paleo-shellfish-pregnant, X-multi-allergen-6.
  • Category avoidance: C-pork, C-alcohol, C-red_meat-mixed.

What CI actually proves, and what it doesn’t

Be careful reading the green checkmark. The suite groups its checks into invariant classes (INVARIANT_CLASSES in the same module), and CI does not enforce every class on every row:

  • The solve-grain invariants — I-ABSENT (the excluded concept never appears in the list), I-ZEROGRAM (a per-person exclusion forces that person’s grams to zero), I-FLOOR (the list still meets its item floor or warns honestly), and I-WARN (degradation surfaces a warning code) — run on every declaring row, by construction. Every matrix row gets a solve, so there is no drift there.

  • The surface-grain invariants — I-SUGGEST (swap candidates and pairing chips carry no excluded concept) and I-VARIANT (the exclusions hold on a shuffled variant solve) — are enforced in CI on a sampled subset only. The registry is SAMPLED_INVARIANT_ROWS, and the rule is recorded there verbatim:

    I-SUGGEST/I-VARIANT are enforced in CI on the SAMPLED_INVARIANT_ROWS rows only; the supported-scope doc mirrors this rule.

    Today that subset is A-milk for both I-SUGGEST and I-VARIANT. I-APPROVE (a restriction added after a solve blocks approval) is sampled on X-vegan-treenut-infant and C-pork; I-INFANT-GUARD (an emptied infant table returns a 503 rather than silently under-protecting) is sampled on P-infant.

So: the absent-set properties are proven broadly; the swap/pairing/variant checks are proven on the named sample and are believed-good elsewhere, not CI-proven everywhere. That is the honest reading.

Not yet: kosher & halal

Mirabelly does not support kosher or halal in v1, and we are not going to approximate them. The three diets we do model — vegetarian, vegan, paleo — are computed from biological-origin and plant-family facets. Kosher and halal are not functions of biological origin: they turn on certification, slaughter method, supervision, and ingredient provenance that our catalog does not carry. Deriving a kosher or halal claim from “this is animal-derived” would be a guess dressed up as a rule, and for a religious-observance restriction a guess is worse than an honest “not yet.” So we leave them out until we can model them properly. There is no kosher or halal option in the person form today, and that absence is deliberate.

Outside the contract: branded add-ins

Branded and convenience add-ins are outside the best-effort allergen and restriction filtering. When you add a branded or convenience category to a week, it is a display-and-nutrition offset only — it carries no curated ingredient facets, so the resolver has nothing to check it against. A milk-allergic household can add a dairy convenience category and nothing will stop it, because the net never sees it.

The product says this at the point of use — the add-in picker carries the byte-locked notice “Branded add-ins aren’t checked against your allergens or restrictions yet — always check the label before you buy.” The rule for you is the same one on the label: check it yourself. This boundary is the reason branded add-ins are kept visibly separate from solver-chosen items.

Outside the contract: weekly extras

Weekly extras are user-authored free text — a label, a quantity, and an aisle, with no linked concept. They are yours to add on top of the list. Because an extra has no canonical_id, it never enters the coverage math and it never passes through the restriction resolver — it is outside the fail-closed contract entirely. Mirabelly does not check an extra against anyone’s allergens, diet, or infant rules; it just prints what you typed. Treat extras exactly as you would a handwritten line on a paper list.

Infants: the <24-month boundary

Infant protection in v1 is real but bounded, and the bound has a gap we are naming on purpose.

Detection: a person is treated as an infant when age_months is set and under 24, or age_months is blank and age_years is under 2 (derive_infant_months). For those infants, the seeded InfantExclusion rules apply — honey and botulism risks, choking hazards, high-mercury fish, and so on — enforced per-person as a zero-gram bound on that infant’s allocation, so the shared list can still carry honey for the adults while the infant’s share is forced to zero.

The gap: several infant rules are written to older ages than detection reaches. InfantExclusion.max_age_months goes up to 36, and some choking-hazard rules are phrased “for children under 4” — but detection stops at 24 months. That means a child between 24 and 47 months (2 and 3 years old) receives no infant exclusions at all, even though rules exist that would apply to them. This is a known limitation, not an oversight: it was reviewed and the decision was to document it, not to widen detection in v1. If you have a two- or three-year-old with a choking-hazard concern, do not rely on the list to filter for it.

Dormant: baby_safe

You may find a baby_safe field in the recipe model. It is dormant. It exists only as Recipe.baby_safe, populated once by an offline classifier, and nothing reads it — no router, service, schema, or grocery-list path consults it. The actual infant mechanism is InfantExclusion (described above), not baby_safe. We are documenting the field here so no one mistakes it for a live safeguard; if you are looking for infant filtering, it is not this.

Pregnancy and lactation

Mirabelly adjusts a deliberately small, named set of nutrient targets for pregnancy and lactation. We would rather tell you exactly which handful of nutrients move than let you assume the whole target set is pregnancy-aware — it is not yet.

The adjusted nutrients. For a pregnant person we raise four targets — iron, folate (as DFE), calcium, and iodine. For a lactating person we raise three — iron, folate (as DFE), and iodine. Calcium is pregnancy-only in our overlay, so the lactation adjustment omits it on purpose. The scorecard says this in the same words you see in the app: “We adjust [name]‘s iron, folate, calcium, and iodine targets for pregnancy (our data covers ages 19–50) … Other nutrient targets aren’t pregnancy-adjusted yet.” Every other nutrient keeps its standard target; the overlay does not touch it.

Ages 19–50 only. The overlay data covers ages 19–50. Outside that band — a pregnant or lactating person aged 14–18, for instance — the nutrient targets are not adjusted at all today, and that person gets the standard targets for their age. The app states this plainly rather than hiding it: “Our pregnancy nutrient targets cover ages 19–50, so [name]‘s nutrient targets aren’t pregnancy-adjusted.” Extending the overlay down to the 14–18 band is a recorded data-curation follow-up, not something that ships in v1.

The calorie estimate is separate. Apart from the nutrient targets, the calorie prefill adds a flat +340 kcal for pregnancy and +330 kcal for lactation, and — unlike the nutrient overlay — it applies at all ages, including the 14–18 band that gets no nutrient adjustment. It is a single mid-pregnancy estimate, not a trimester-by-trimester curve: the app presents it as “a mid-pregnancy value” for pregnancy and “a typical nursing increase” for lactation. Treat it as an editable starting point, not a medical calorie target.

Category avoidance v1

Category avoidance is the “avoid X” chips. In v1 the list is closed and contains exactly four categories: pork, beef, red_meat, alcohol. These are preferences, not medical restrictions and not allergens — see docs/api/category_avoidance.md for the full contract, the storage shape, and the boundary against allergen semantics. If a category you need is not one of those four, it is not supported in v1.

Nutrient ceilings (“too high”)

The report reads the top of the range as well as the bottom. When a person’s intake for a nutrient lands over its upper intake limit (UL) — or, for sodium, over the amount the dietary guideline recommends — the report marks that nutrient “too high”, sitting alongside the usual met / marginal / low picture and the tally (for example, 12 met · 3 marginal · 1 low · 1 too high).

This is report-only, and the boundary matters. The flag is a reading of the list you already have; it does not change how the list gets built. The solver does not hard-block a list for crossing a ceiling — it will still hand you that list, and the “too high” note is how we tell you about the breach rather than a wall that stops it. And because the flag lives on the read side, legacy lists are not re-scored: a list solved before this flag existed keeps its original report and never retroactively grows a “too high” mark. You only see the ceiling flag on lists scored after it shipped.

Gluten-free

Gluten-free ships in v1 as a diet filter, and it is built to fail closed. When you mark a person gluten-free, the solver keeps only the concepts we have reviewed and flagged as gluten-free and leaves everything else off that person’s list. A food that happens to contain no gluten but carries no flag is excluded, not included — the filter would rather drop a food it has not vetted than let a gluten-bearing one onto the list. That has a real cost, and we are naming it: an un-flagged food that would in fact have been fine is left out until someone curates it. The filter errs toward leaving things out; it never errs toward letting gluten in.

How many foods carry the flag today. As of this writing, 529 pool-eligible concepts carry the gluten-free flag — that is the size of the curated set a gluten-free household is solved against. The count is pinned in CI (the gluten_free flag tripwire), so it does not drift on its own: if it moves, that is a deliberate re-curation backed by recorded evidence, not an accident. A dropped flag would quietly shrink a gluten-free basket rather than fail loudly, which is exactly why the number is pinned.

This is a diet filter, not an allergen guarantee. Gluten is not one of the Big-9 allergens, and the gluten-free diet is a separate system from the allergen net described above. The allergen net takes a checked allergen — say wheat — and expands it through curated facets; the gluten-free diet instead reads a per-concept curated flag. Do not read a gluten-free result as an allergen clearance, and do not read the wheat allergen as covering gluten — they are two different mechanisms answering two different questions. As always, check the label yourself.

What this closes: rye, barley, and malt. Before the gluten-free diet existed, the only lever a celiac user had was the wheat allergen checkbox, and that expands the wheat facet only. Rye, barley, and malt carry no wheat facet, so checking “wheat” alone left rye bread, barley, and malt-bearing foods sitting on the list. The gluten-free diet is the thing that catches them: it is keyed on the gluten-free flag, which curators apply across the gluten grains, not wheat alone. If you need to avoid gluten, reach for the gluten-free diet, not the wheat allergen.

© 2026 Mirabelly
Pricing Help What we cover Terms Privacy llms.txt

Optional analytics help us see which parts of Mirabelly are working — tied only to your account id, never your dietary details or your children’s information.