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>
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):
- Remove
MealPlan.votes(votes are reachable viaMealPlan.items[*].votes). - Add
meal_plan_idFK toMealPlanVoteand a back-ref. Requires migration. - Specify
primaryjoin="MealPlan.id == foreign(remote(MealPlanVote.meal_plan_item_id))"viaMealPlanItem— 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-clickand 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 SQLAlchemyBase.metadata.create_allonce 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).