Files
admin a0b16f7418 fix: address adversarial review blockers
All §1 consensus blockers and §2 high-risk gaps resolved:

Schema fixes:
- Remove RecipeIngredient join table, use JSONB for ingredients
- Add family_member table for per-voter approval tracking
- Add all ENUMs for status fields (no loose VARCHAR)
- Add CHECK constraints (household_size, rating 1-5, day_of_week)
- Add name_lower for case-insensitive ingredient matching
- Add grocery_item → ingredient FK
- Fix day_of_week to ISO-8601 (1=Monday, 7=Sunday)
- Remove calorie_target (nutrition is non-goal)

Approval flow redesign:
- Email link → confirmation page (GET), not auto-approve
- Actual vote is POST from confirmation page
- Per-voter tokens (single-use, 72h TTL)
- Record which member voted

Auth model:
- VPN-only for admin endpoints
- Session-based for family web UI

Docker hardening:
- Remove direct port exposure for backend/frontend
- nginx is sole entrypoint
- Add docker-compose.dev.yml for local dev

Skeleton fixes:
- Add missing Pantry.tsx page
- Add missing index.html (Vite entrypoint)
- Add package-lock.json
- Fix SQLAlchemy 2 text() for raw SQL
- Remove create_all from startup (use migrations)
- Configure Alembic properly

Docs updates:
- Update Lucky URL to luckysupermarkets.com
- Add WCAG 2.1 AA accessibility target
- Update family profile with correct mushroom preferences
- Add external dependencies list to SPEC

Verification:
- docker compose config: PASS
- docker compose build backend: PASS
- docker compose build frontend: PASS
- backend import: PASS
- alembic context: PASS
2026-05-04 20:11:05 -07:00

10 KiB
Raw Permalink Blame History

Review Synthesis — Three Reviewers Compared

Sources: docs-claude.md (Claude, doc review), docs-gpt5.5.md (GPT-5.5, doc review), repo-gpt5.5.md (GPT-5.5, repo/code review).


1. Strong Consensus (all reviewers, blocking)

# Issue Claude GPT docs GPT repo
C1 Recipe↔ingredient modeled two ways (JSONB column AND recipe_ingredient join) — must pick one B3 #2 #2 (also broken back_populates in code)
C2 No authentication anywhere; admin + approval endpoints publicly mutable B4 #4 #8
C3 Email approval token model is unsafe (no per-voter scoping, no TTL/single-use, GET mutates state) B5 #4 #9
C4 SECRET_KEY is a ghost/unsafe default with no documented consumer m6 #11 #10
C5 Schema enums missing — status/reason fields are loose VARCHARs M4 (implied #15) #15
C6 Docker compose publishes backend (8000) and frontend (3000) directly — bypasses nginx m3 #12
C7 Production deployment claims (TLS, rate limiting, prod compose) are not implemented #10 #13
C8 Phase status is contradictory — ORIENTATION says Phase 1 complete, plan boxes unticked, code is partial skeleton m1 #1 (confirms repo is skeleton)

Implication: these eight items are the minimum gate before any further code.


2. Partial Agreement (2 of 3)

# Issue Found by Missed by
P1 Migration story muddled — create_all at startup vs Alembic-as-source-of-truth; Alembic not configured in repo GPT docs #5, GPT repo #6 Claude (only flagged "SQLAlchemy or Alembic" wording — M5)
P2 API endpoint contract conflicts (POST /api/meals/{id}/approve vs GET /api/approve/{token}, deny variants) GPT docs #3, GPT repo #7 Claude did not enumerate route mismatches
P3 Scraping under-specified — feasibility, selectors, anti-bot, normalization Claude M1, GPT docs #7 GPT repo (out of scope)
P4 Phase ordering hides risk behind plumbing; need spike for scrape + email before 12-table schema Claude M2, GPT docs #7 GPT repo
P5 Shopping-list math (units, conversion, pantry subtraction, "1 bunch cilantro") not implementable from current docs Claude M4 (partial), GPT docs #9 GPT repo
P6 Accessibility absent despite family UI + email Claude m2, GPT docs #16 GPT repo
P7 Env var docs inconsistent across .env.example, ORIENTATION, RUNNING, plan Claude m6 (SECRET_KEY only), GPT docs #11 GPT repo
P8 Tech stack drift / "CRA or Vite" wording when project uses Vite (none) GPT docs #13, GPT repo (lockfile/index.html)
P9 Health endpoint uses raw db.execute("SELECT 1") — needs text() under SQLAlchemy 2 GPT docs #14, GPT repo #11

3. Unique Findings (only one reviewer)

Claude only

  • B1 — Family-profile math doesn't close. Spec says 2 adults + 2 children, then references "two adults and one child do NOT like mushrooms" + "one adult likes mushrooms" = 3 adults. Single most concrete worked example in the spec, and it doesn't typecheck.
  • B2 — No family_member table despite per-person preferences and asymmetric voting in SPEC §6/§7. Both adults share one approval token; whoever clicks first wins.
  • M3 — APScheduler + multi-worker uvicorn will fire weekly scrape N times. No leader election, no --workers 1 pin, no separate scheduler container.
  • M6 — Silence-as-consent. "If both approve OR no response → confirmed" plus the >80% approval-rate metric means the metric is gamed by spam-foldering.
  • M7 — calorie_target column with non-goal status; spice_level with no spec mention. Dead schema or wrong spec.
  • M1 — Lucky URL probably wrong. LUCKY_CA_URL=https://www.luckyncal.com; chain's actual site is luckysupermarkets.com. Needs verification.
  • B5.1 — Email GET prefetch. SafeLinks/Proofpoint/GMail prefetch every link → silent auto-approval before human reads. This is a known failure mode; fix is confirmation page that POSTs.
  • M4 day_of_week 0=Monday disagrees with both Postgres EXTRACT(DOW) (0=Sun) and ISO-8601 (1=Mon).
  • M4 total_time_minutes documented as computed but stored as plain INTEGER — will drift.
  • M4 ingredient.name UNIQUE is case-sensitive → "Carrots" ≠ "carrots".
  • M4 grocery_item has no FK to ingredient — the bridge between scraping and planning has no place to live.
  • M8 inline base64 vs CDN for email images — base64 inflates ~33% and GMail filters >102KB.

GPT-5.5 docs only

  • #3 (precision) — exact route-by-route mismatch table between implementation-plan and ARCHITECTURE for approve/deny endpoints.
  • #15 — data-retention windows have no job owner / schedule / FK-cascade design (archive-to-JSON breaks feedback FKs).
  • #17 — observability is aspirational; no log schema, correlation IDs, error taxonomy.

GPT-5.5 repo only (Claude could not have caught these — doc-only review)

  • #1 — Backend import broken. main.py:4 imports family_profile, ingredient, recipe, … as submodules; all classes actually live in models/__init__.py. App likely does not start.
  • #2 — Broken back_populates. RecipeIngredient.recipe = relationship(..., back_populates="recipe_ingredients") but Recipe has no recipe_ingredients attr → mapper config will fail.
  • #3 — Missing frontend/src/pages/Pantry.tsx but imported by App.tsx:5.
  • #4 — npm ci in Dockerfile but no package-lock.json.
  • #5 — Missing frontend/index.html (Vite entrypoint).
  • #6 — No alembic.ini / alembic/ despite docs and requirements.
  • #11 — db.execute("SELECT 1") will fail under SQLAlchemy 2.
  • #16 — Trailing-slash inconsistency between router prefixes and docs.

4. Where Reviewers Conflict

Almost no direct contradictions — the reviewers are largely orthogonal. Two soft tensions:

  1. Severity of phase-status drift. Claude treats it as Minor (m1, doc hygiene). GPT-5.5 docs treats it as Blocking (#1). GPT-5.5 repo evidence (skeleton present but broken) supports the GPT-5.5 framing — this should be Blocking.
  2. Recipe↔ingredient framing. Claude frames as "pick one"; GPT-5.5 repo shows code already has both with a broken relationship. The decision is no longer abstract — choosing JSONB requires deleting RecipeIngredient table; choosing the join requires removing the JSONB column. Either way, code already has to change.

5. Combined Blocker List (deduplicated, ranked)

Resolve in order. Items 16 must land before Phase 2 schema work; 79 before any API/feature code.

  1. Reconcile household-vs-members model. Decide family_member table or single shared mailbox. Fixes B1+B2 and unblocks per-voter approval scoping.
  2. Pick recipe-ingredient representation. Delete the loser from both docs and code. Fixes C1 / GPT repo #2.
  3. Define MVP auth. VPN-only, basic-auth at proxy, or app sessions — pick one and put it in SPEC §8. Fixes C2 + scopes the SECRET_KEY question (C4).
  4. Redesign approval flow. GET → confirmation page that POSTs; per-voter token; explicit TTL; single-use; record which member voted. Fixes C3 + Claude B5.1.
  5. Reconcile phase status. One canonical status doc reflecting actual code. Fixes C8.
  6. Stabilize the skeleton (GPT repo Phase 0). Fix model imports, broken back_populates, missing index.html / Pantry.tsx / lockfile, text("SELECT 1"), configure Alembic, remove create_all from startup. Without this, "Phase 1 complete" is false.
  7. Schema cleanup pass: enums/CHECKs for status fields; CITEXT or lowercase for ingredient names; grocery_item.ingredient_id FK; CHECK that adult+child = household_size; GENERATED column for total_time_minutes; pick day_of_week convention; resolve calorie_target / spice_level vs spec.
  8. Compose hardening: intra-network only for backend/frontend; nginx is sole entrypoint.
  9. Decide scheduler topology (separate container, or --workers 1 pinned and documented) before APScheduler is wired.

6. Additional Investigation Needed

These were not resolved by any reviewer and require concrete checks:

  • Verify the Lucky URL. curl -I https://www.luckyncal.com and confirm it serves the expected weekly-ad surface; if wrong, every Phase 4 task is built on sand.
  • Run the GPT-5.5 verification matrix (repo-gpt5.5.md §"Verification Matrix") to confirm which build/start commands actually fail today. Reviewers asserted the skeleton is broken but did not run it.
  • Audit Lucky California ToS and recipe-site copyright before the scraper exists, not after.
  • Confirm SendGrid (or alternative) is the chosen mail provider and what the deliverability profile to GMail/iCloud is — silence-as-consent (Claude M6) is dangerous if delivery rate <100%.
  • Decide whether the project's "self-hosted" framing is load-bearing. If yes, every external dependency (SendGrid, AI image API, Let's Encrypt, scraped images) must be enumerated in SPEC §8 with an offline-fallback story. If no, drop the framing.
  • Threat-model the email approval token end-to-end: leak via forwarded mail, archived inbox, screenshot, mail-scanner prefetch. Claude flagged it; no reviewer wrote a model.
  • Define the planner's hard-vs-soft constraints and a no-solution fallback (GPT docs #8). Neither doc nor code has scoring weights or tie-breakers.
  • Define unit conversion / "1 bunch cilantro" / "to taste" handling before pantry and shopping-list models freeze.
  • Decide WCAG target (2.1 AA per user CLAUDE.md) and which surfaces it covers (web UI yes; email — accessible HTML + plain-text alt).

7. Net Assessment

The two adversarial doc reviews are largely complementary, not redundant: GPT-5.5 caught contract-level mismatches (routes, env vars, migration tooling) and Claude caught semantic/correctness issues (the family math, the prefetch problem, scheduler concurrency, silence-as-consent). The GPT-5.5 repo review is the only source of evidence about what actually runs — and it indicates the skeleton labeled "Phase 1 complete" likely does not start.

Combined readiness: ~4/10. The product intent is clear, but at least three load-bearing design decisions (member model, ingredient representation, auth model) and one infrastructure gap (Alembic + skeleton import bugs) must be resolved before Phase 2.