Files
Meal-Planner/.agent/phase-summaries/r2b-blockers.md
T
adminandClaude Opus 4.7 8e89f793d5 feat: phase r1+r2 recovery + r3-0 swiftly api ingestion
R1 stabilization: pytest harness with transactional db fixture, smoke
+ alembic + auth + scrape + approval + swiftly tests, github actions
ci yaml. Bearer-token admin auth + signed-cookie session for family
ui mutations. Async POST /api/admin/scrape (BackgroundTasks, returns
202). Path canonicalization (no /list, /planned suffixes). DATABASE_URL
fail-fast on empty.

R2 deferred-risk spikes: live lucky california fetch (R2-A), full
email+per-voter approval click round trip with single-use enforcement
(R2-B, console email backend, sendgrid stub).

R3-0 phase 3 redesign: replaced playwright html scraper with requests
based swiftly json api client. 17 categories, ~10k products per scrape,
upsert by (source, external_id). 401 surfaces actionable token-refresh
message via ScrapeLog.error_message.

Pre-existing defects fixed: shopping_list.py syntax error blocking app
import, MealPlan.votes orphan relationship, JSONB(astext=True) invalid
kwarg, missing requests dep, calorie_target schema drift, every SQLEnum
needed values_callable, 0001 had empty downgrade(), seed had duplicate
ingredient rows.

Migrations added: 0003 grocery_item.description, 0004 family_profile.
calorie_target, 0005 grocery_item.external_id + source + composite index.

Verified: 31/31 pytest green, alembic upgrade->downgrade->upgrade clean,
frontend npm run build clean, live scrape 9,960 grocery_item rows in 36s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:08:19 -07:00

3.1 KiB

R2-B blockers — pre-existing defects discovered by the spike

The R2-B email + per-voter approval round-trip cannot complete the end-to-end DB-backed proof until both of the following pre-existing bugs are fixed. Both are OUT OF SCOPE for R2-B per the task brief.

Blocker 1: model relationship bug — MealPlan.votes

File: backend/app/models/__init__.py:201

class MealPlan(Base):
    ...
    votes = relationship("MealPlanVote", back_populates="meal_plan", cascade="all, delete-orphan")

But MealPlanVote has no meal_plan relationship and no FK to meal_plan.id — only to meal_plan_item.id (line 235).

Symptom: sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship MealPlan.votes fires the moment ANY mapper is configured (i.e. on the first ORM use of any model). Blocks every flow, not just the vote flow.

Repro (with schema in place):

DATABASE_URL='postgresql://...' python -c "
from app import models
from sqlalchemy.orm import configure_mappers
configure_mappers()  # raises NoForeignKeysError
"

Fix options (for whichever agent owns models):

  1. Remove MealPlan.votes (votes are reachable via MealPlan.items[*].votes).
  2. Add meal_plan_id FK to MealPlanVote and a back-ref. Requires migration.
  3. Specify primaryjoin="MealPlan.id == foreign(remote(MealPlanVote.meal_plan_item_id))" via MealPlanItem — viewonly only.

Option 1 is least invasive and matches existing usage in meals.py (no code reads MealPlan.votes).

Blocker 2: migration uses invalid kwarg — JSONB(astext=True)

File: backend/alembic/versions/0001_initial_migration.py:101

sa.Column('ingredients', postgresql.JSONB(astext=True), nullable=False),

astext is not a valid JSONB.__init__ kwarg in SQLAlchemy 2.x.

Symptom: alembic upgrade head fails with: TypeError: JSON.__init__() got an unexpected keyword argument 'astext'.

Impact: backend/tests/test_alembic.py::test_alembic_upgrade_head_roundtrip fails. backend/tests/conftest.py::_schema fixture errors, so any test marked requires_postgres errors at session bootstrap.

Fix: drop the kwarg. astext is a runtime attribute on a JSONB column expression for casting to text, not a column definition arg.

sa.Column('ingredients', postgresql.JSONB(), nullable=False),

Effect on R2-B

  • Parts 1 (services), 2 (routes), and the unit-test portion of Part 4 are complete and verified (3 unit tests pass, 3 DB-backed tests skip cleanly on no-PG environments).
  • Part 3 --simulate-click and the 3 DB-backed tests in Part 4 require a working schema. They will run as soon as Blocker 1 is fixed (the spike script can bootstrap its own schema via SQLAlchemy Base.metadata.create_all once the relationship resolves).

Sandbox cleanup note

While diagnosing Blocker 1, this agent ran Base.metadata.create_all against mealplanner-db-1 (192.168.144.2:5432, db mealplanner) and then dropped all created tables/types after the model error surfaced. Final state: only alembic_version (matches pre-spike state).