Public Access
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
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
# Adversarial Documentation Review — Claude
|
||||
|
||||
Scope: `docs/SPEC.md`, `docs/ARCHITECTURE.md`, `docs/database-schema.md`, `docs/implementation-plan.md`, `docs/ORIENTATION.md`, `docs/RUNNING.md`, `README.md`, `meal-planner-plan.md`, `.env.example`, `docker-compose.yml`. Reviewed independently of the GPT-5.5 review.
|
||||
|
||||
Tone: adversarial. Findings ranked Blocking / Major / Minor. Cite file:line where useful.
|
||||
|
||||
---
|
||||
|
||||
## Verdict
|
||||
|
||||
The docs read as a coherent first draft, but the spec, architecture, and schema do not agree with each other on three load-bearing surfaces: **how recipes link to ingredients, how approvals identify a voter, and what the authentication model is** (there isn't one). The mushroom/family-profile section is mathematically self-contradictory. Several "self-hosted" claims are undermined by hard external dependencies (SendGrid, AI image APIs, scraping third-party sites). Phase 1 was committed before these contradictions were resolved, so the migration written next will encode whichever side of the contradiction the implementer happens to read first.
|
||||
|
||||
Recommendation: **do not start Phase 2 (DB & Models) until SPEC §6, ARCHITECTURE §2.2/§3, and `database-schema.md` §2.3 are reconciled** — the migration is the wrong place to make these decisions.
|
||||
|
||||
---
|
||||
|
||||
## Blocking
|
||||
|
||||
### B1. Family profile is internally inconsistent — the math doesn't close
|
||||
|
||||
`docs/SPEC.md:79-88` states household = 2 adults + 2 children. Then:
|
||||
- "One adult likes mushrooms" → 1 adult
|
||||
- "One child is OK with mushrooms" → 1 child
|
||||
- "Two adults and one child do NOT like mushrooms" → 2 adults + 1 child
|
||||
|
||||
Adult count: 1 + 2 = **3 adults claimed in a 2-adult household**. Either the spec is wrong about the household composition, the preference list is wrong, or this is meant to illustrate a structure (per-person preferences) that the schema does not actually model — see B2.
|
||||
|
||||
This is the single concrete worked example in the entire spec. If it doesn't typecheck, neither does anything downstream that consumes it (planner constraints, denial reasons, "never suggest" semantics).
|
||||
|
||||
### B2. There is no `family_member` (or `user`) table — but the spec is per-person
|
||||
|
||||
SPEC §6 talks about individual preferences ("one adult likes…"). SPEC §7 sends the proposal to "both adults" and treats their votes asymmetrically ("if either denies → swap"). Yet `database-schema.md` has only `family_profile` (a household row) and `feedback` keyed by `meal_plan_item_id` with `UNIQUE(meal_plan_item_id)` (`database-schema.md:147`).
|
||||
|
||||
Consequences:
|
||||
- Cannot record *which* family member likes/dislikes mushrooms.
|
||||
- Cannot record *which* adult denied a meal — both share one `approval_token` per `meal_plan_item` (`database-schema.md:104`). Whoever clicks first wins; the other can silently overwrite, and the system can't tell them apart.
|
||||
- The `UNIQUE` on feedback means a household gets exactly one rating per meal — a regression from per-member learning that the spec implies.
|
||||
- "Send to both adult email addresses" (`implementation-plan.md:234`) has no schema support — addresses live only in env vars (`FAMILY_EMAIL_1`, `FAMILY_EMAIL_2`), which means you cannot extend to in-laws/guests/kid-with-phone without a redeploy.
|
||||
|
||||
Fix the model before writing the migration. Either add `family_member(id, family_profile_id, name, role, email)` and key tokens/feedback off it, or explicitly downgrade the spec to "one shared mailbox, one shared opinion."
|
||||
|
||||
### B3. Recipe↔ingredient relationship is documented two contradictory ways
|
||||
|
||||
- `ARCHITECTURE.md:201, 241` — N:N via `recipe_ingredient` join table, drawn in the ASCII diagram.
|
||||
- `database-schema.md:66` — `recipe.ingredients JSONB NOT NULL`, no join table defined.
|
||||
|
||||
These are not equivalent. JSONB precludes the FK indexes shown later (`idx_…` in `database-schema.md` §3.2) for ingredient-driven planner queries ("find recipes that use this on-sale item"), which is the **core meal-planner query**. The planner inputs at `ARCHITECTURE.md:94-108` cannot be served efficiently by a JSONB column without a GIN index that no one has specified, and even then ingredient-level operations (substitution, pantry subtraction, sale lookup) become awkward.
|
||||
|
||||
Pick one. If JSONB is genuinely the choice, the schema doc must justify it and add the GIN index; if the join table is the choice, the JSONB column must come out of the recipe row. Until then, `Recipe` SQLAlchemy model is being asked to be both shapes simultaneously.
|
||||
|
||||
### B4. There is no authentication anywhere in the system
|
||||
|
||||
Search across all docs for `auth`, `login`, `password`, `session`, `user` returns: nothing actionable. Yet:
|
||||
- The web UI has a "Pantry Manager", "Approval Center", and "Admin/Settings" page (`ARCHITECTURE.md:159-165`).
|
||||
- "Remote access via reverse proxy" is a stated requirement (`SPEC.md:128-129`, `ARCHITECTURE.md:337`).
|
||||
- `docker-compose.yml` exposes port 80/443 publicly.
|
||||
- `POST /api/admin/scrape` and `GET /api/admin/logs` (`ARCHITECTURE.md:267-268`) are unauthenticated.
|
||||
|
||||
"Family of 4 inside the house" is not a security boundary once `RUNNING.md` describes Let's Encrypt + a public domain. Anyone on the internet who guesses the URL can trigger scrapes, dump logs (potentially containing emails and SendGrid IDs from `email_log`), edit the pantry, or change the family profile. The `SECRET_KEY=change-me-to-a-random-secret-key` placeholder in `.env.example` is referenced by no doc — what does it sign? If JWTs, where? If session cookies, against what user table?
|
||||
|
||||
The "VPN option" and "IP whitelist capability" mentioned at `ARCHITECTURE.md:339-340` are not a substitute for auth on a system that explicitly accepts approval/denial state mutations from email links.
|
||||
|
||||
### B5. Approval-by-email is fragile and probably exploitable
|
||||
|
||||
`ARCHITECTURE.md:271-273`:
|
||||
```
|
||||
GET /api/approve/{token} → Mark meal approved
|
||||
GET /api/deny/{token} → Show denial reason form
|
||||
```
|
||||
|
||||
Two distinct problems:
|
||||
1. **GET mutates state.** Many corporate/AV email scanners (Microsoft Defender SafeLinks, Proofpoint URL Defense, GMail link prefetch) and some clients pre-fetch every link in an email to scan it. Every meal will be silently auto-approved before the human sees the message. This is a known, common, well-documented failure mode for "click the link to approve" patterns. Fix: the email link must lead to a confirmation page that POSTs.
|
||||
2. **Token model gives no per-voter accountability.** See B2. Combined with B4 (no login on the confirmation page), once a token leaks (forwarded email, screenshot in a chat, archived in a mailbox) anyone can vote. `approval_token_expires` is nullable (`database-schema.md:105`), so absent application logic the token is permanent.
|
||||
|
||||
Also: `denial_reason VARCHAR(50)` with the values listed in `database-schema.md:106` should be a CHECK or enum; otherwise denial-reason analytics in the learning step will be polluted by typos.
|
||||
|
||||
### B6. "Self-hosted, no external cloud services *except SendGrid*" is undermined
|
||||
|
||||
`SPEC.md:130-131` makes a clean claim, then immediately exempts SendGrid, then `ARCHITECTURE.md` adds:
|
||||
- "AI Image Service (DALL-E, Anthropic, etc.)" (`ARCHITECTURE.md:351-356`)
|
||||
- "Centralized logging optional (Papertrail, Datadog)" (`ARCHITECTURE.md:397`)
|
||||
- "Caddy or Nginx with Let's Encrypt" (an external CA) (`ARCHITECTURE.md:338`)
|
||||
- Recipe site scraping (third-party, see B7)
|
||||
- Hotlinked image URLs from third-party recipe sites (`ARCHITECTURE.md:347-350`)
|
||||
|
||||
If the user genuinely cares about self-hosting (the README leads with it), at minimum SPEC §8 needs to enumerate every external dependency, not just SendGrid. If they don't, drop the "self-hosted, no external services" framing — it's marketing copy at this point.
|
||||
|
||||
---
|
||||
|
||||
## Major
|
||||
|
||||
### M1. Scraping legality / ToS unaddressed
|
||||
|
||||
`SPEC.md:132-136` and `ARCHITECTURE.md §2.1` describe scraping Lucky California's weekly ad, product catalog, *and* "public recipe sites". "Respect robots.txt and rate limiting" is the entire policy. There is no:
|
||||
- Review of Lucky California's ToS (most US grocery retailers prohibit scraping).
|
||||
- Plan for Cloudflare/bot-protection bypass (Playwright is mentioned, suggesting the author already knows static fetch will be blocked — `RUNNING.md:225` even has a manual Playwright retry incantation).
|
||||
- Recipe-site copyright analysis (image hotlinking + caching scraped images is copying protected content — `ARCHITECTURE.md:347` calls it out as "stored as URLs, not downloaded" but `ARCHITECTURE.md:354` says generated images are "cached in database", and `recipe.image_source = 'scraped'` (`database-schema.md:56`) implies persistence).
|
||||
|
||||
A homelab project for one family is unlikely to attract a takedown, but a doc that says "this is the architecture" should at least name the risk.
|
||||
|
||||
Also worth verifying: `LUCKY_CA_URL=https://www.luckyncal.com` (`.env.example:14`, `RUNNING.md:39`). The actual chain's site is `luckysupermarkets.com`. `luckyncal.com` *might* be the franchise's California-specific site, but no doc cites the source. If the URL is wrong, every Phase 4 task is built on sand.
|
||||
|
||||
### M2. Phase ordering buries the hard problems behind weeks of plumbing
|
||||
|
||||
`implementation-plan.md` puts the actually risky work — scraping (Phase 4), planner algorithm (Phase 6), email/approval (Phase 7) — *after* full backend skeleton, full schema, full API surface, and most of the React UI. Each of those phases is the one most likely to fail or force schema changes; deferring them maximizes rework cost.
|
||||
|
||||
A more honest plan would build a minimal "can I scrape one ad page" and "can I email + receive an approval click" spike before committing to a 12-table schema and 6 React pages. Otherwise you'll do Phase 2 twice.
|
||||
|
||||
### M3. APScheduler + multiple workers will misfire
|
||||
|
||||
`implementation-plan.md:154` picks APScheduler. `docker-compose.yml` and the Dockerfile docs don't mention worker count, but `uvicorn --workers N` is the default FastAPI deployment. APScheduler in-process will fire the weekly scrape *N times* if N>1. No doc mentions a leader-election scheme, a separate `scheduler` service, or `--workers 1`. Either pin to one worker (and document it) or move scheduling to a dedicated container / cron.
|
||||
|
||||
Same hazard: per-worker state for retry counters, image caches, etc.
|
||||
|
||||
### M4. Schema has avoidable defects
|
||||
|
||||
- `meal_plan_item.day_of_week` 0=Monday (`database-schema.md:101`). Postgres `EXTRACT(DOW)` is 0=Sunday; ISO-8601 is 1=Monday. Pick ISO and document it; "0=Monday" is the worst of three options because it silently disagrees with both common conventions.
|
||||
- `recipe.total_time_minutes` is documented as "Computed: prep + cook" (`database-schema.md:59`) but stored as a plain INTEGER. Make it a Postgres `GENERATED ALWAYS AS … STORED` column or remove it. As written, it will drift the first time someone backfills.
|
||||
- `family_profile.household_size` with separate `adult_count`/`child_count` and no CHECK that they sum (`database-schema.md:21-23`). Trivial CHECK; add it.
|
||||
- `feedback.meal_plan_item_id UNIQUE` (`database-schema.md:147`) — see B2.
|
||||
- `home_pantry.unit` and `ingredient.unit` (`database-schema.md:41, 125`) are duplicate truth. The pantry-vs-recipe unit-mismatch problem (cans vs oz vs cups) is real but not addressed — `implementation-plan.md:184` waves at "handle unit conversions" with no design.
|
||||
- `grocery_item` has no FK to `ingredient` (`database-schema.md §2.9`). The "map products to existing ingredients or create new ones" step (`implementation-plan.md:147`) is the entire bridge between scraping and planning, and the schema gives it no place to live. A `grocery_item.ingredient_id` (nullable until matched) is the obvious missing column.
|
||||
- `ingredient.name UNIQUE` (`database-schema.md:37`) is case-sensitive. "Carrots" ≠ "carrots" ≠ "Carrot". Either lower-case-on-write or use `CITEXT`.
|
||||
- `denial_reason`, `image_source`, `meal_plan.status`, `feedback.denial_reason`, `email_log.status`, `scrape_log.status` — all are `VARCHAR(N)` with values enumerated in prose. Use Postgres ENUMs or CHECK constraints; otherwise "approved " (with a trailing space) is a valid status.
|
||||
|
||||
### M5. Migration story is muddled
|
||||
|
||||
`database-schema.md:251-257`: "Use SQLAlchemy or Alembic for schema management." These aren't alternatives — Alembic *is* SQLAlchemy's migration tool. Pick the wording. Also: `implementation-plan.md` Phase 1.1 commits to Alembic + initial migration *before* Phase 2 defines the SQLAlchemy models. Either Phase 1 ships an empty migration and Phase 2 generates the real one, or the order is wrong. As written, the doc says do them simultaneously.
|
||||
|
||||
If the Phase 1 commit (`1328ec3`) already created models or a migration, that drift is already in the repo and the docs need to catch up.
|
||||
|
||||
### M6. "If both approve OR no response → meal confirmed" is silence-as-consent
|
||||
|
||||
`SPEC.md:114`. Combined with B5, this means the default state of an unread email is **approved**. For a system whose pitch is "stop forcing me to log into apps", that's coherent — but the spec should *say* that explicitly, and the success metric "Approval rate >80% of proposed meals approved without changes" (`SPEC.md:166`) is meaningless if silence counts as approval. You'll hit 100% the first week the email lands in spam.
|
||||
|
||||
### M7. "Calorie target" appears in the schema but is a non-goal
|
||||
|
||||
`database-schema.md:26` defines `calorie_target INTEGER`. `SPEC.md:55` lists "Nutrition tracking beyond high-level calorie awareness" as a non-goal. Either the column has no consumer (dead schema), or §4 is wrong. Same tension between `database-schema.md:65` (`spice_level`) and the spec's silence on heat preferences.
|
||||
|
||||
### M8. Image strategy contradicts itself
|
||||
|
||||
`SPEC.md:54`: "AI image generation as primary source (scraped images first)" is listed as a *non-goal*, suggesting AI-as-primary is rejected. `ARCHITECTURE.md §6.2` and `implementation-plan.md §11.2` then build AI image generation as a fallback. "Non-goal: X as primary" and "build X as fallback" are not in conflict, but the wording in SPEC §4 reads as if AI generation is out entirely. Tighten the language so an implementer doesn't shave the AI-fallback task on a literal reading.
|
||||
|
||||
Also: the email image strategy says "Embed images via CDN URL or inline base64" (`ARCHITECTURE.md:359`). Inline base64 inflates emails by ~33% and is filtered by GMail above 102KB; "or" hides a real decision.
|
||||
|
||||
---
|
||||
|
||||
## Minor
|
||||
|
||||
### m1. Doc hygiene
|
||||
|
||||
- `implementation-plan.md:431` references `docs/implemenation-plan.md` (typo: missing `t`). Self-referential broken link.
|
||||
- `README.md` "Quick Start" section is empty per the indexer (just a header) — the file references `docs/RUNNING.md` for setup but offers no `docker compose up` one-liner up front.
|
||||
- `meal-planner-plan.md` is described as "Original planning file (may be superseded)" in `ORIENTATION.md`. Either delete it or freeze it with a banner; leaving "may be superseded" in a Key Files table guarantees someone reads it as canonical.
|
||||
- ORIENTATION says "Phase 1 complete" but `implementation-plan.md` checkboxes are all unticked. The plan is the canonical task list per the user's CLAUDE.md memory rules; update it.
|
||||
|
||||
### m2. Operational gaps not in any doc
|
||||
|
||||
- No backup/restore procedure (postgres volume, recipe images if ever downloaded, scraped catalog).
|
||||
- No CI plan. `pytest` is in `requirements.txt`-equivalent but no test phase, no test layout, no CI yaml. Phase verification commands are all `curl localhost:…` — fine for a smoke test, useless as regression coverage.
|
||||
- No accessibility commitment. The user's global CLAUDE.md mandates WCAG 2.1 AA for UI work; the React UI plan never mentions it.
|
||||
- No log redaction. `email_log` stores recipient emails; `scrape_log` may capture HTML; both are retained 30-90 days (`database-schema.md §5`). PII handling not addressed.
|
||||
- No rate-limiting design beyond "Rate limiting on approval endpoints" (`ARCHITECTURE.md:336`). What limit? Per IP? Per token? Per family?
|
||||
|
||||
### m3. Docker-compose exposure
|
||||
|
||||
`docker-compose.yml` (per the indexer) publishes ports for backend (8000), frontend (3000), *and* nginx (80/443). On a remote-accessible host, 8000 and 3000 should be `expose:` (intra-compose only), not `ports:` (host-published). Otherwise nginx routing is bypassable by hitting :8000 directly, and the rate limits on /api/* don't apply. Worth a one-line fix in the next iteration but doc-impacting because `RUNNING.md` and `ARCHITECTURE.md` both describe nginx as "the" entrypoint.
|
||||
|
||||
### m4. Success metrics aren't measurable
|
||||
|
||||
`SPEC.md §10`:
|
||||
- "Family consistently uses the system weekly" — undefined ("consistent" = ?).
|
||||
- ">80% approved without changes" — broken by silence-as-consent (M6).
|
||||
- "After 4 weeks, system should not propose previously denied meals" — that's a correctness invariant, not a metric. Should hold week 1.
|
||||
- "Within 150% of equivalent grocery-store meal" — equivalent how? No baseline source.
|
||||
|
||||
Either drop the metrics section or make them queryable from the schema.
|
||||
|
||||
### m5. Future features are mixed into the implementation plan
|
||||
|
||||
`implementation-plan.md §12.3` (WhatsApp) and `§12.4` (recipe scraping) are explicitly out-of-scope per `SPEC.md §4`/`§11`. Putting them in the implementation plan creates pressure to do them. Move to a separate "future" doc or remove the checkboxes.
|
||||
|
||||
### m6. `SECRET_KEY` ghost variable
|
||||
|
||||
`.env.example` defines `SECRET_KEY=change-me-to-a-random-secret-key`. No doc says what consumes it. If it's for FastAPI session/JWT signing, the auth model (B4) needs to exist first. If it's vestigial, drop it — placeholder secrets in `.env.example` get copied into real `.env` files unchanged more often than anyone admits.
|
||||
|
||||
---
|
||||
|
||||
## Cross-doc contradictions, summarized
|
||||
|
||||
| Topic | Doc A says… | Doc B says… |
|
||||
|---|---|---|
|
||||
| Recipe ingredients | join table `recipe_ingredient` (ARCHITECTURE.md:201, 241) | `recipe.ingredients JSONB` column (database-schema.md:66) |
|
||||
| Calorie tracking | non-goal (SPEC.md:55) | `calorie_target` column (database-schema.md:26) |
|
||||
| Self-hosting | "no external services except SendGrid" (SPEC.md:130) | DALL-E, Let's Encrypt, recipe scraping, hotlinked images, optional Datadog (ARCHITECTURE.md §6, §8.2) |
|
||||
| Reverse proxy | nginx (ARCHITECTURE.md §5.2) | "Caddy or Nginx" (ARCHITECTURE.md:338, implementation-plan.md:64) |
|
||||
| AI image generation | non-goal as primary (SPEC.md:54) | full Phase 11.2 fallback (implementation-plan.md §11.2) |
|
||||
| Meal types | "dinner only for MVP" (implementation-plan.md:198) | breakfast/lunch/dinner enum (database-schema.md:102) |
|
||||
| Migration tool | "SQLAlchemy or Alembic" (database-schema.md:251) | Alembic, Phase 1.1 (implementation-plan.md) |
|
||||
| Phase 1 status | complete (ORIENTATION.md) | all unchecked (implementation-plan.md) |
|
||||
| Voter identity | sent to "both adults", denial swaps (SPEC.md §7) | one shared `approval_token` per meal_plan_item, no member table (database-schema.md §2.5) |
|
||||
|
||||
---
|
||||
|
||||
## Suggested order of operations before any more code
|
||||
|
||||
1. **Resolve B1 + B2.** Decide whether the system models the household as one entity or as members. This is a 1-line decision that propagates everywhere.
|
||||
2. **Resolve B3.** Pick join-table or JSONB. Then update both docs.
|
||||
3. **Resolve B4.** Decide auth model: VPN-only (drop the public-internet framing), basic-auth at nginx, or app-level sessions. Pick one and put it in SPEC §8.
|
||||
4. **Resolve B5.** Switch email approval to a confirmation-page POST; document token TTL and per-voter scoping (which depends on B2).
|
||||
5. **Tighten SPEC §6 and §10** so the worked example typechecks and the metrics are queryable.
|
||||
6. *Then* regenerate the migration and proceed to Phase 2.
|
||||
|
||||
Phase 2 written today against the current docs will produce a schema that contradicts itself in three places and will be revised within two phases. The cheapest review is the one done before the migration ships.
|
||||
@@ -0,0 +1,153 @@
|
||||
# Adversarial Documentation Review
|
||||
|
||||
## Verdict
|
||||
|
||||
Do not move directly into implementation from these docs without resolving the blockers below. The documentation is directionally useful, but it currently mixes aspirational design, stale implementation status, contradictory API/schema definitions, and missing security requirements. If implementation starts as-is, likely outcomes are rework in Phase 2, broken startup/API assumptions, and unauthenticated admin or approval workflows that become hard to retrofit.
|
||||
|
||||
## Blocking Findings
|
||||
|
||||
### 1. Phase/status docs contradict current implementation state
|
||||
|
||||
`docs/ORIENTATION.md:21-29` says Phase 1 is complete and SQLAlchemy models are created, while `docs/implementation-plan.md:9-19` still lists Phase 1 setup as unchecked. `docs/implementation-plan.md:70-97` says Phase 2 models/migrations are not done, but `backend/app/models/__init__.py` already contains model classes. This creates an immediate execution hazard: implementers cannot tell whether Phase 2 is greenfield, repair work, or migration alignment.
|
||||
|
||||
Required before implementation:
|
||||
- Rewrite phase status into one canonical source of truth.
|
||||
- Separate “implemented skeleton exists” from “verified and production-ready”.
|
||||
- Add explicit acceptance criteria for Phase 2 based on current code, not original plan checkboxes.
|
||||
|
||||
### 2. Database schema is internally inconsistent and conflicts with current models
|
||||
|
||||
`docs/database-schema.md:45-73` defines `recipe.ingredients` as JSONB, but `docs/ARCHITECTURE.md:238-244` and `docs/ORIENTATION.md:100-105` describe a `recipe_ingredient` junction relationship. Current code includes both `Recipe.ingredients` JSONB and a `RecipeIngredient` table, while `RecipeIngredient.recipe = relationship("Recipe", back_populates="recipe_ingredients")` has no matching `Recipe.recipe_ingredients` relationship in the model. This is not just documentation drift; it points at an implementation design conflict that will break ORM mapping or duplicate ingredient truth.
|
||||
|
||||
Required before implementation:
|
||||
- Choose either normalized `recipe_ingredient` as canonical or JSONB as canonical.
|
||||
- Document why the chosen model supports unit conversion, shopping-list aggregation, pantry subtraction, and ingredient matching.
|
||||
- Update schema docs, architecture diagrams, and model expectations to match one design.
|
||||
|
||||
### 3. API endpoint definitions conflict across docs
|
||||
|
||||
`docs/implementation-plan.md:110-115` specifies `POST /api/meals/{id}/approve`, `GET /api/deny/{token}`, and `POST /api/deny/{token}`. `docs/ARCHITECTURE.md:252-273` specifies `POST /api/meals/{id}/deny`, `GET /api/approve/{token}`, and token deny routes. `docs/RUNNING.md:140-151` references admin routes that are not protected in the docs. These differences matter because frontend routing, email templates, and backend routers will depend on exact paths.
|
||||
|
||||
Required before implementation:
|
||||
- Publish one OpenAPI-aligned endpoint table.
|
||||
- Distinguish authenticated web actions from unauthenticated email-token actions.
|
||||
- Define request/response schemas, status codes, redirects, token expiry errors, and idempotency for approve/deny flows.
|
||||
|
||||
### 4. Security model is missing for externally reachable workflows
|
||||
|
||||
The docs state remote access via nginx/Caddy (`docs/SPEC.md:127-130`) and tokenized email approval links (`docs/ARCHITECTURE.md:270-273`), but `docs/ORIENTATION.md:190-194` says “JWT-free for MVP” without defining replacement controls. Admin endpoints like `POST /api/admin/scrape` and log views are documented (`docs/implementation-plan.md:124-127`, `docs/RUNNING.md:140-151`) but no authentication, authorization, CSRF protection, rate limits, or network restrictions are specified.
|
||||
|
||||
Required before implementation:
|
||||
- Define MVP auth explicitly: session, basic auth behind reverse proxy, signed email tokens, or VPN-only deployment.
|
||||
- Require strong random token generation, hashing-at-rest or equivalent protection, expiry, single-use/rotation behavior, and audit logging for email approvals.
|
||||
- Protect admin endpoints before exposing remote access.
|
||||
- Define CORS, trusted hosts, proxy headers, secure cookies if sessions are used, and secret management for `SECRET_KEY`.
|
||||
|
||||
### 5. Migration strategy conflicts with runtime table creation
|
||||
|
||||
Docs repeatedly require Alembic migrations (`docs/RUNNING.md:104-115`, `docs/implementation-plan.md:91-97`), but current backend startup calls `Base.metadata.create_all(bind=engine)` in `backend/app/main.py:17`. Starting implementation from the docs without addressing this will create unmanaged tables, then make Alembic autogeneration noisy or unsafe.
|
||||
|
||||
Required before implementation:
|
||||
- Document whether Phase 2 must remove runtime `create_all`.
|
||||
- Define initial migration ownership and whether existing dev DBs can be dropped.
|
||||
- Add a verification command that proves migrations, not app startup, create schema.
|
||||
|
||||
### 6. Current code appears incompatible with documented model layout
|
||||
|
||||
`docs/implementation-plan.md:72-85` suggests one model per table and `backend/app/models/__init__.py` with imports. Current `backend/app/main.py:4` imports `from app.models import family_profile, ingredient, recipe, meal_plan, home_pantry, feedback, grocery_item, scrape_log, email_log`, but actual `backend/app/models/__init__.py` defines all classes in one file and no such submodules exist. The docs do not warn implementers that the current skeleton may not start.
|
||||
|
||||
Required before implementation:
|
||||
- Verify and document current startup status.
|
||||
- Decide whether model files will be split or imports fixed.
|
||||
- Make docs describe the actual current state, including known broken skeleton areas.
|
||||
|
||||
## High-Risk Gaps
|
||||
|
||||
### 7. Scraping feasibility is under-specified
|
||||
|
||||
The docs say scrape Lucky California weekly ads and product catalog while respecting robots.txt (`docs/SPEC.md:132-136`), but there is no feasibility spike, selector strategy, anti-bot handling policy, cache policy, legal/compliance note, or fallback data-entry schema. `docs/implementation-plan.md:163` only verifies “10+ sale items”, which does not prove stable mapping to ingredients, price units, sale windows, or aisles.
|
||||
|
||||
Recommendation:
|
||||
- Add a Phase 4 spike before building dependent meal-planning logic.
|
||||
- Define scraped product normalization rules: package size, price-per-unit, sale terms, loyalty-card requirements, store location, unavailable/out-of-stock states.
|
||||
- Define manual fallback UX and data model before scraper failure handling is implemented.
|
||||
|
||||
### 8. Meal-planning algorithm lacks deterministic requirements
|
||||
|
||||
`docs/ARCHITECTURE.md:91-109` and `docs/implementation-plan.md:189-215` describe constraints at a high level, but do not define scoring weights, hard vs soft constraints, conflict resolution, reproducibility, or failure behavior when no valid 7-day plan exists. “No mushrooms for 3/4 members” is listed, but not modeled as per-person preferences or household-level exclusion.
|
||||
|
||||
Recommendation:
|
||||
- Define hard constraints, soft preferences, scoring weights, tie-breakers, and fallback behavior.
|
||||
- Add example input/output fixtures for at least one valid plan and one no-solution case.
|
||||
- Decide whether disliked ingredients exclude whole-family meals or permit optional substitutions.
|
||||
|
||||
### 9. Shopping-list math is not implementable from current docs
|
||||
|
||||
Shopping-list generation requires unit conversion, ingredient normalization, recipe scaling, pantry subtraction, and store item matching. Docs mention these features (`docs/implementation-plan.md:180-184`, `docs/implementation-plan.md:305-327`) but do not define canonical units, conversion tables, rounding rules, partial pantry quantities, or substitutions.
|
||||
|
||||
Recommendation:
|
||||
- Add unit normalization requirements before implementing recipes and pantry.
|
||||
- Define output rules for ambiguous items like “1 bunch cilantro”, “to taste salt”, and recipe-specific prepared ingredients.
|
||||
- Add tests using real recipe-like data, not only CRUD checks.
|
||||
|
||||
### 10. Deployment docs promise production features that compose file does not provide
|
||||
|
||||
`docs/RUNNING.md:47-57` documents SSL certs, while `docker-compose.yml` mounts only `./nginx/nginx.conf` and no SSL directory. `docs/RUNNING.md:75-80` references `docker-compose.prod.yml`, which does not exist. `docs/ARCHITECTURE.md:329-336` says nginx handles SSL and rate limiting, but current `nginx/nginx.conf` only proxies `/api/` and serves SPA fallback over port 80.
|
||||
|
||||
Recommendation:
|
||||
- Mark production deployment as not implemented or add missing deployment docs later.
|
||||
- Remove commands referencing nonexistent compose files until they exist.
|
||||
- Define dev vs prod networking, TLS termination, and rate-limit boundaries explicitly.
|
||||
|
||||
### 11. Environment-variable docs are inconsistent and include unsafe examples
|
||||
|
||||
`docs/ORIENTATION.md:128-147`, `docs/RUNNING.md:27-45`, `docs/implementation-plan.md:516-533`, and `.env.example` use overlapping but not identical environment variables. Examples include API-key-like prefixes (`SG...`, `sk-...`) and `SECRET_KEY=change-me-to-a-random-secret-key`, but docs do not say which vars are required by backend startup, frontend build, or optional integrations.
|
||||
|
||||
Recommendation:
|
||||
- Add a canonical env var table with required/optional/default/scope.
|
||||
- Avoid realistic secret prefixes in docs where possible.
|
||||
- Add startup validation expectations and safe local-dev defaults.
|
||||
|
||||
## Medium-Risk Issues
|
||||
|
||||
### 12. Documentation contains stale and misspelled file paths
|
||||
|
||||
`docs/implementation-plan.md:417-480` places `RUNNING.md` at project root, but actual docs list it under `docs/RUNNING.md`. The same tree misspells `implemenation-plan.md` at `docs/implementation-plan.md:431`. This will cause agent/tool confusion during implementation.
|
||||
|
||||
### 13. Tech-stack docs are already version stale
|
||||
|
||||
Docs repeatedly say React 18 (`README.md:73`, `docs/ORIENTATION.md:63`, `docs/ARCHITECTURE.md:168`), while current frontend dependencies use React `^18.2.0`. That is not wrong today, but the docs lack a dependency-update policy and lockfile expectations. More important: `docs/implementation-plan.md:43` suggests Create React App or Vite, but current project uses Vite, so CRA should be removed to avoid divergent setup.
|
||||
|
||||
### 14. Operational docs assume endpoints that are not proven to exist
|
||||
|
||||
`docs/RUNNING.md:280-288` documents `/health/db`; current implementation has this route, but its SQLAlchemy call uses a raw string execute in `backend/app/main.py:28`, which may fail under SQLAlchemy 2 without `text("SELECT 1")`. Docs should avoid declaring health checks verified until command output is captured.
|
||||
|
||||
### 15. Data retention lacks implementation mechanics
|
||||
|
||||
`docs/database-schema.md:265-274` defines retention windows, but no job owner, schedule, archival storage, or privacy behavior is described. “Archive to JSON, delete rows” for meal plans can break feedback history if FK behavior is not designed.
|
||||
|
||||
### 16. Accessibility is barely addressed despite family-facing UI
|
||||
|
||||
The docs require a non-technical family web UI and email workflow, but do not specify WCAG targets, keyboard behavior, color contrast, readable print views, or email-client fallbacks beyond plain text. Approval/denial links in email need accessible labels and safe confirmation UX.
|
||||
|
||||
### 17. Observability requirements are aspirational
|
||||
|
||||
`docs/ARCHITECTURE.md:386-404` lists log categories and future monitoring, but no structured log schema, correlation IDs, error taxonomy, or alert thresholds. Scraper/email failures are core product risks, so this should be in MVP docs, not future polish.
|
||||
|
||||
## Suggested Gate Before Implementation
|
||||
|
||||
Resolve these docs before writing Phase 2 code:
|
||||
|
||||
- One canonical project status page that reflects actual current code and broken areas.
|
||||
- One canonical schema with either JSONB recipe ingredients or normalized `recipe_ingredient`, not both as competing truths.
|
||||
- One canonical API contract, preferably generated or mirrored from OpenAPI.
|
||||
- MVP security section covering admin routes, approval tokens, remote access, secrets, CORS, rate limiting, and audit logs.
|
||||
- Migration policy that removes or explicitly forbids runtime `create_all` outside throwaway local dev.
|
||||
- Deployment doc split into current local-dev support vs future production support.
|
||||
- Verification matrix with exact commands and expected output per phase.
|
||||
|
||||
## Implementation Readiness Rating
|
||||
|
||||
Current documentation readiness: 5/10.
|
||||
|
||||
The docs are good enough to understand product intent, but not good enough to drive implementation safely. Main risk is not missing detail; main risk is contradictory detail that will cause agents or engineers to implement incompatible schema/API/security assumptions.
|
||||
@@ -0,0 +1,261 @@
|
||||
# Repository Analysis
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The repository is an early skeleton, not an implementation-ready Phase 1 baseline. Product intent is well documented, but current code cannot be treated as a verified foundation: backend imports are inconsistent with file layout, frontend imports missing source files, migration infrastructure is absent despite Alembic being documented, and all API endpoints are placeholders.
|
||||
|
||||
Readiness rating: 4/10.
|
||||
|
||||
Recommended next move: stabilize the skeleton before implementing feature phases. Do not start meal planning, scraping, email, or shopping-list logic until app startup, build, migrations, and API contract are corrected.
|
||||
|
||||
## Current Repo Shape
|
||||
|
||||
Implemented or present:
|
||||
- `docker-compose.yml` with `backend`, `frontend`, `db`, and `nginx` services.
|
||||
- FastAPI app skeleton under `backend/app`.
|
||||
- SQLAlchemy model classes colocated in `backend/app/models/__init__.py`.
|
||||
- Placeholder API routers for profile, recipes, meals, shopping list, pantry, and admin.
|
||||
- Vite/React/Tailwind frontend skeleton.
|
||||
- Nginx configs under both `nginx/nginx.conf` and `frontend/nginx.conf`.
|
||||
- Extensive docs under `docs/` plus `README.md`.
|
||||
|
||||
Missing or not present:
|
||||
- `frontend/index.html`.
|
||||
- `frontend/package-lock.json`, despite Dockerfile using `npm ci`.
|
||||
- `frontend/src/pages/Pantry.tsx`, despite being imported by `App.tsx`.
|
||||
- `backend/alembic.ini` and `backend/alembic/`.
|
||||
- Backend `schemas/`, `services/`, and `scraper/` directories.
|
||||
- Tests.
|
||||
- Authn/authz layer.
|
||||
- OpenAPI contract beyond FastAPI's generated placeholder routes.
|
||||
|
||||
## Blocking Findings
|
||||
|
||||
### 1. Backend likely fails during import because model module layout is inconsistent
|
||||
|
||||
`backend/app/main.py:4` imports `family_profile`, `ingredient`, `recipe`, `meal_plan`, `home_pantry`, `feedback`, `grocery_item`, `scrape_log`, and `email_log` from `app.models`. Those submodules do not exist. All model classes are currently defined in `backend/app/models/__init__.py`.
|
||||
|
||||
Impact: backend startup is likely broken before FastAPI can serve `/health`.
|
||||
|
||||
Fix direction:
|
||||
- Either split models into the imported module files, or remove those submodule imports and import the package/classes consistently.
|
||||
- Add a backend startup smoke test before continuing any feature work.
|
||||
|
||||
### 2. SQLAlchemy relationships contain at least one broken back-populates reference
|
||||
|
||||
`backend/app/models/__init__.py:247` sets `RecipeIngredient.recipe = relationship("Recipe", back_populates="recipe_ingredients")`, but `Recipe` does not define `recipe_ingredients`. This is likely to fail mapper configuration once SQLAlchemy evaluates relationships.
|
||||
|
||||
Impact: even if import paths are fixed, ORM use can fail at runtime.
|
||||
|
||||
Fix direction:
|
||||
- Add `Recipe.recipe_ingredients` or remove/replace the relationship.
|
||||
- Decide whether recipe ingredients are normalized via `recipe_ingredient`, stored in `recipe.ingredients` JSONB, or intentionally duplicated with clear ownership.
|
||||
|
||||
### 3. Frontend build is blocked by a missing imported page
|
||||
|
||||
`frontend/src/App.tsx:5` imports `./pages/Pantry`, and `frontend/src/App.tsx:32` routes `/pantry` to it. No `frontend/src/pages/Pantry.tsx` file exists.
|
||||
|
||||
Impact: TypeScript/Vite build should fail immediately.
|
||||
|
||||
Fix direction:
|
||||
- Add a minimal `Pantry.tsx` placeholder, or remove the route/import until the page exists.
|
||||
|
||||
### 4. Frontend Docker build is blocked by missing npm lockfile
|
||||
|
||||
`frontend/Dockerfile:5-6` copies `package*.json` then runs `npm ci`. There is no `frontend/package-lock.json`.
|
||||
|
||||
Impact: `npm ci` fails without a lockfile, so the frontend container cannot build reproducibly.
|
||||
|
||||
Fix direction:
|
||||
- Generate and commit `package-lock.json`, or change Dockerfile to use `npm install` for the skeleton phase. Prefer lockfile.
|
||||
|
||||
### 5. Vite app is missing required HTML entrypoint
|
||||
|
||||
No `frontend/index.html` exists. Vite expects an HTML entrypoint at project root unless customized.
|
||||
|
||||
Impact: local dev/build is blocked even after fixing `Pantry.tsx`.
|
||||
|
||||
Fix direction:
|
||||
- Add standard Vite `index.html` pointing to `/src/main.tsx`.
|
||||
|
||||
### 6. Alembic is documented and installed but not configured
|
||||
|
||||
`backend/requirements.txt:4` includes Alembic and docs repeatedly call `alembic upgrade head`, but no `backend/alembic.ini` or `backend/alembic/` exists. Meanwhile `backend/app/main.py:17` calls `Base.metadata.create_all(bind=engine)` at import/startup.
|
||||
|
||||
Impact: schema ownership is confused. Runtime table creation can mask missing migrations and produce unmanaged dev DB state.
|
||||
|
||||
Fix direction:
|
||||
- Create Alembic config and first migration.
|
||||
- Remove `create_all` from normal app startup once migrations exist.
|
||||
- Add a documented migration verification path.
|
||||
|
||||
## Important Findings
|
||||
|
||||
### 7. API routes are placeholders with no schemas or persistence
|
||||
|
||||
All API functions currently return static `{"message": ...}` payloads. Examples: `backend/app/api/profile.py:8-15`, `backend/app/api/recipes.py:8-20`, `backend/app/api/meals.py:8-20`, and `backend/app/api/admin.py:8-20`.
|
||||
|
||||
Impact: the route shape exists, but no request models, response models, validation, DB reads/writes, or errors exist. Treat these as route stubs, not usable endpoints.
|
||||
|
||||
Fix direction:
|
||||
- Add Pydantic schemas before endpoint implementation.
|
||||
- Implement one resource end-to-end first, probably profile or ingredients, to establish patterns.
|
||||
|
||||
### 8. Admin endpoints are unauthenticated
|
||||
|
||||
`backend/app/api/admin.py` exposes scrape trigger, logs, and test email routes without auth. Docs anticipate remote access via reverse proxy.
|
||||
|
||||
Impact: once implemented, these routes could trigger external scraping, send email, and expose operational logs.
|
||||
|
||||
Fix direction:
|
||||
- Define auth before implementing admin behavior.
|
||||
- At minimum, gate admin routes behind a dependency and environment-configured secret/session/proxy auth.
|
||||
|
||||
### 9. Email approval security is not implemented and not modeled yet
|
||||
|
||||
Models include `approval_token` and `approval_token_expires` in `MealPlanItem`, but no token route, validation, hashing, single-use behavior, or audit trail exists. Current routes approve/deny by meal ID, not token.
|
||||
|
||||
Impact: approval workflow cannot safely be exposed via email links.
|
||||
|
||||
Fix direction:
|
||||
- Decide token route contract before frontend/email work.
|
||||
- Use high-entropy signed or stored tokens with expiry and replay handling.
|
||||
- Keep meal-ID approval routes authenticated if they remain.
|
||||
|
||||
### 10. `SECRET_KEY` has an unsafe default
|
||||
|
||||
`backend/app/config.py:13` sets `SECRET_KEY: str = "dev-secret-key"`. `.env.example` says to change it, but code does not enforce change outside dev.
|
||||
|
||||
Impact: future signed tokens/sessions could be deployed with a known secret.
|
||||
|
||||
Fix direction:
|
||||
- Make `SECRET_KEY` required for non-development environments.
|
||||
- Document environment mode and startup validation.
|
||||
|
||||
### 11. SQLAlchemy 2 health query may fail
|
||||
|
||||
`backend/app/main.py:28` calls `db.execute("SELECT 1")`. With SQLAlchemy 2, textual SQL should use `text("SELECT 1")`.
|
||||
|
||||
Impact: `/health/db` can report failure even when DB is reachable.
|
||||
|
||||
Fix direction:
|
||||
- Use `from sqlalchemy import text` and `db.execute(text("SELECT 1"))`.
|
||||
|
||||
### 12. Docker Compose exposes backend and frontend directly plus nginx
|
||||
|
||||
`docker-compose.yml` maps backend `8000:8000`, frontend `3000:80`, and nginx `80:80`/`443:443`. This is convenient for dev but contradicts a production reverse-proxy boundary if deployed unchanged.
|
||||
|
||||
Impact: backend bypasses nginx controls such as future rate limiting/auth headers unless ports are removed or bound to localhost.
|
||||
|
||||
Fix direction:
|
||||
- Split dev and prod compose files.
|
||||
- In prod, expose only reverse proxy and keep backend/frontend on internal network.
|
||||
|
||||
### 13. Nginx SSL and rate limiting are not implemented
|
||||
|
||||
`nginx/nginx.conf` and `frontend/nginx.conf` only provide SPA fallback and `/api/` proxying over HTTP. No TLS cert mounts, rate limits, security headers, request size limits, or proxy timeout policy exist.
|
||||
|
||||
Impact: deployment docs overstate production readiness.
|
||||
|
||||
Fix direction:
|
||||
- Keep current nginx config labeled as local/dev only.
|
||||
- Add production config later with TLS, headers, and route-specific limits.
|
||||
|
||||
## Maintainability Findings
|
||||
|
||||
### 14. Model file is too large for early iteration and hides domain decisions
|
||||
|
||||
`backend/app/models/__init__.py` contains all models and several unresolved design choices. This makes migrations, ownership, and imports harder to reason about.
|
||||
|
||||
Recommendation:
|
||||
- Split per aggregate/table after schema ownership is decided, or keep colocated temporarily but remove false submodule imports.
|
||||
- Add tests that import and configure all mappers.
|
||||
|
||||
### 15. Schema constraints are only partially represented
|
||||
|
||||
Some documented constraints are in code, such as `day_of_week BETWEEN 0 AND 6` and feedback rating range. Others are missing or underspecified, such as family counts, valid status enums, meal type values, and never-suggest rules.
|
||||
|
||||
Recommendation:
|
||||
- Prefer Python enums plus DB checks for status/meal type/reason fields.
|
||||
- Add validation at Pydantic layer before DB writes.
|
||||
|
||||
### 16. API prefix behavior may produce trailing-slash-only endpoints
|
||||
|
||||
Routers are included with prefixes like `/api/profile`, and router paths use `/`. This exposes `/api/profile/` rather than `/api/profile` unless FastAPI redirects. Docs mostly specify no trailing slash.
|
||||
|
||||
Recommendation:
|
||||
- Decide canonical trailing-slash style and update routes/docs together.
|
||||
|
||||
### 17. No repository-level quality gates exist
|
||||
|
||||
There are no tests, no backend lint/format config, no frontend lockfile, and no CI config. Docs mention pytest, Black, isort, ESLint, and Prettier, but config and scripts are incomplete.
|
||||
|
||||
Recommendation:
|
||||
- Add minimal smoke tests first: backend import/app creation, mapper configuration, frontend build.
|
||||
- Add formatting/lint configs only when used by scripts/CI.
|
||||
|
||||
## Documentation Alignment
|
||||
|
||||
The separate documentation review in `Review/docs-gpt5.5.md` covers doc-specific contradictions. Repo analysis confirms several documentation risks against current files:
|
||||
|
||||
- Phase status is overstated: skeleton is present but not verified.
|
||||
- Alembic workflow is documented but absent.
|
||||
- API route contract differs between docs and code.
|
||||
- Production deployment docs exceed current compose/nginx capability.
|
||||
- Model/schema docs do not resolve JSONB ingredients vs normalized ingredients.
|
||||
|
||||
## Recommended Remediation Order
|
||||
|
||||
### Phase 0: Make skeleton start and build
|
||||
|
||||
- Fix backend model imports.
|
||||
- Fix SQLAlchemy `recipe_ingredients` relationship mismatch.
|
||||
- Fix `/health/db` SQLAlchemy 2 textual SQL.
|
||||
- Add `frontend/index.html`.
|
||||
- Add or remove `Pantry.tsx` route.
|
||||
- Add frontend lockfile or adjust Dockerfile.
|
||||
- Run backend import smoke test and frontend build.
|
||||
|
||||
### Phase 1: Establish schema/migration baseline
|
||||
|
||||
- Decide ingredient representation.
|
||||
- Configure Alembic.
|
||||
- Generate initial migration.
|
||||
- Remove runtime `create_all` from normal startup.
|
||||
- Add seed-data strategy.
|
||||
|
||||
### Phase 2: Establish API and security baseline
|
||||
|
||||
- Define canonical OpenAPI contract.
|
||||
- Add Pydantic schemas.
|
||||
- Add auth dependency for admin and authenticated web routes.
|
||||
- Define approval-token routes and behavior.
|
||||
- Add CORS/trusted-host/proxy assumptions.
|
||||
|
||||
### Phase 3: Implement first vertical slice
|
||||
|
||||
- Implement profile or ingredient CRUD end-to-end.
|
||||
- Add DB-backed tests.
|
||||
- Wire frontend API client for one real endpoint.
|
||||
- Use this slice as pattern for recipes, pantry, and meal plans.
|
||||
|
||||
## Verification Matrix To Add
|
||||
|
||||
Minimum commands expected before declaring foundation complete:
|
||||
|
||||
```bash
|
||||
docker compose config
|
||||
docker compose build backend
|
||||
docker compose build frontend
|
||||
docker compose up -d db
|
||||
docker compose run --rm backend python -c "from app.main import app; print(app.title)"
|
||||
docker compose run --rm backend alembic upgrade head
|
||||
docker compose run --rm backend pytest
|
||||
docker compose run --rm frontend npm run build
|
||||
```
|
||||
|
||||
These commands may not pass today; they are the gate that should define “Phase 1 complete”.
|
||||
|
||||
## Final Assessment
|
||||
|
||||
The repo is valuable as a product specification plus rough skeleton. It should not be considered a working app foundation yet. Biggest immediate risk is building new features on top of a skeleton that likely does not start or build. Stabilize imports, frontend entrypoints, Docker builds, and migrations first; then implement one small DB-backed vertical slice before expanding into scraper/email/planner complexity.
|
||||
@@ -0,0 +1,205 @@
|
||||
# Review Consensus — Feedback for Originating Agent
|
||||
|
||||
This document consolidates findings from three independent adversarial reviews of the MealPlanner project documentation and current repository state. Use this as the canonical input for the next planning iteration. Every item here was either agreed by multiple reviewers or supported by direct repo evidence.
|
||||
|
||||
Reviewers:
|
||||
- **Claude** — adversarial documentation review (`Review/docs-claude.md`)
|
||||
- **GPT-5.5 (docs)** — adversarial documentation review (`Review/docs-gpt5.5.md`)
|
||||
- **GPT-5.5 (repo)** — repository / code review (`Review/repo-gpt5.5.md`)
|
||||
|
||||
---
|
||||
|
||||
## Headline
|
||||
|
||||
The product intent is clear and the documentation is a coherent first draft, but **three load-bearing design decisions are unresolved** and **the "Phase 1 complete" skeleton likely does not start or build**. Proceeding to Phase 2 (DB & Models) without resolving the items in §1 will produce a schema and migration that contradict the spec in at least three places, and will be reworked within two phases.
|
||||
|
||||
Combined readiness: **~4/10**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Consensus Blockers — must resolve before any further code
|
||||
|
||||
These were flagged by all three reviewers (or by both doc reviewers and confirmed in code).
|
||||
|
||||
### 1.1 Recipe ↔ Ingredient relationship is modeled two contradictory ways
|
||||
- `database-schema.md:66` — `recipe.ingredients JSONB NOT NULL`.
|
||||
- `ARCHITECTURE.md:201, 241` — N:N via `recipe_ingredient` join table.
|
||||
- **Code already has both**, with a broken `RecipeIngredient.recipe = relationship("Recipe", back_populates="recipe_ingredients")` where `Recipe` has no matching attribute (`backend/app/models/__init__.py:247`). Mapper config will fail at runtime.
|
||||
- **Action:** pick one. JSONB precludes the FK indexes needed for the core "find recipes that use this on-sale item" query; the join table makes ingredient substitution / pantry subtraction tractable. Delete the loser from both docs *and* code.
|
||||
|
||||
### 1.2 No authentication exists anywhere in the system
|
||||
- Spec/architecture describe public-internet access via reverse proxy + Let's Encrypt.
|
||||
- Admin endpoints (`POST /api/admin/scrape`, `GET /api/admin/logs`), pantry mutation, family profile edit, and approval/deny endpoints are all unauthenticated.
|
||||
- `SECRET_KEY=change-me-to-a-random-secret-key` in `.env.example` is referenced by no doc.
|
||||
- **Action:** decide MVP auth model — VPN-only (drop the public-internet framing), basic-auth at proxy, or app-level sessions. Put the choice in SPEC §8 before writing any more endpoints. This decision also scopes what `SECRET_KEY` signs.
|
||||
|
||||
### 1.3 Email approval flow is unsafe and probably exploitable
|
||||
- `GET /api/approve/{token}` mutates state. Mail scanners (SafeLinks, Proofpoint, GMail prefetch) will silently auto-approve every meal before the human sees the message. **This is a known, common failure mode.**
|
||||
- One token per `meal_plan_item` shared across both adults — no per-voter accountability; whoever clicks first wins.
|
||||
- `approval_token_expires` is nullable → tokens are permanent absent app logic.
|
||||
- **Action:** redesign — email link leads to a confirmation page that POSTs; per-voter token; explicit TTL; single-use; record which member voted. Depends on §1.4.
|
||||
|
||||
### 1.4 No `family_member` table despite per-person spec
|
||||
- SPEC §6/§7 describes individual preferences and asymmetric voting ("if either denies → swap").
|
||||
- Schema has only `family_profile` (one row per household) and `feedback` with `UNIQUE(meal_plan_item_id)` — one rating per household per meal.
|
||||
- The single worked example in SPEC §6 is **mathematically inconsistent**: 2-adult household but text references three distinct adults' mushroom preferences.
|
||||
- **Action:** decide whether the system models the household as one entity or as members. Either add `family_member(id, family_profile_id, name, role, email)` and key tokens/feedback off it, or downgrade the spec to "one shared mailbox, one shared opinion." This is a 1-line decision that propagates everywhere.
|
||||
|
||||
### 1.5 Phase status is contradictory and the skeleton does not work
|
||||
- `ORIENTATION.md` says Phase 1 complete; `implementation-plan.md` checkboxes are all unticked.
|
||||
- `backend/app/main.py:4` imports `family_profile, ingredient, recipe, …` as submodules from `app.models`, but all classes live in `models/__init__.py`. **Import will fail.**
|
||||
- `frontend/src/App.tsx:5` imports `./pages/Pantry`; the file does not exist. **TS/Vite build will fail.**
|
||||
- `frontend/Dockerfile` runs `npm ci`; no `package-lock.json`. **Frontend container will not build.**
|
||||
- No `frontend/index.html` (Vite entrypoint). **Vite will not start.**
|
||||
- No `backend/alembic.ini` or `backend/alembic/` despite docs requiring `alembic upgrade head`.
|
||||
- `backend/app/main.py:17` calls `Base.metadata.create_all(...)` at startup, undermining migration ownership.
|
||||
- `backend/app/main.py:28` uses `db.execute("SELECT 1")` — must be `text("SELECT 1")` under SQLAlchemy 2.
|
||||
- **Action:** before declaring Phase 1 complete, run a verification matrix that proves the skeleton imports, builds, and starts.
|
||||
|
||||
### 1.6 Schema has avoidable correctness defects
|
||||
- All status/reason fields are loose `VARCHAR(N)` — use Postgres ENUMs or CHECK constraints (otherwise `"approved "` with a trailing space is valid).
|
||||
- `ingredient.name UNIQUE` is case-sensitive — "Carrots" ≠ "carrots". Use `CITEXT` or lowercase-on-write.
|
||||
- `grocery_item` has no FK to `ingredient`. The bridge between scraping and planning has no place to live.
|
||||
- `family_profile.adult_count + child_count` not checked against `household_size`.
|
||||
- `recipe.total_time_minutes` documented as computed but stored as plain INTEGER — will drift. Use `GENERATED ALWAYS AS … STORED`.
|
||||
- `meal_plan_item.day_of_week` 0=Monday disagrees with both Postgres `EXTRACT(DOW)` (0=Sun) and ISO-8601 (1=Mon).
|
||||
- `feedback.UNIQUE(meal_plan_item_id)` blocks per-member ratings (see §1.4).
|
||||
- `calorie_target` exists in schema; nutrition is a stated non-goal. Either drop it or update the spec.
|
||||
|
||||
### 1.7 Docker compose exposes backend (8000) and frontend (3000) directly
|
||||
- `nginx` is described as "the" entrypoint, but ports 8000/3000 are host-published, so any rate limit / auth header / TLS at nginx is bypassable.
|
||||
- **Action:** intra-network only for backend/frontend in prod compose; nginx is sole entrypoint. Consider splitting dev vs prod compose files.
|
||||
|
||||
### 1.8 Production deployment claims exceed implementation
|
||||
- `RUNNING.md` references `docker-compose.prod.yml`, SSL cert mounts, rate limiting — none exist.
|
||||
- `nginx/nginx.conf` only proxies `/api/` and serves SPA fallback over HTTP.
|
||||
- **Action:** label current nginx config as local/dev only; remove or clearly mark unimplemented production commands.
|
||||
|
||||
---
|
||||
|
||||
## 2. High-Risk Gaps — agreed by 2 of 3 reviewers
|
||||
|
||||
### 2.1 Migration tooling is muddled
|
||||
"SQLAlchemy or Alembic" is wrong wording (Alembic *is* SQLAlchemy's tool). Phase 1 commits to Alembic + initial migration before Phase 2 defines the models. Code uses `create_all` at startup. **Action:** define a single migration policy; remove `create_all` from normal startup; configure Alembic in Phase 1 with an empty initial migration and let Phase 2 generate the real one.
|
||||
|
||||
### 2.2 API endpoint contract conflicts across docs
|
||||
`implementation-plan.md` and `ARCHITECTURE.md` disagree on approve/deny route shapes (`POST /api/meals/{id}/approve` vs `GET /api/approve/{token}`, etc.). **Action:** publish one OpenAPI-aligned endpoint table; distinguish authenticated web actions from token-based email actions; specify request/response schemas, status codes, redirects, expiry errors, and idempotency.
|
||||
|
||||
### 2.3 Scraping is under-specified and risky
|
||||
- No feasibility spike, selector strategy, anti-bot policy (Playwright is mentioned, suggesting static fetch is already known to fail).
|
||||
- No Lucky California / recipe-site ToS or copyright analysis.
|
||||
- `LUCKY_CA_URL=https://www.luckyncal.com` may be wrong — the chain's actual site is `luckysupermarkets.com`. **Verify before Phase 4.**
|
||||
- `grocery_item` to `ingredient` mapping (the entire scrape→plan bridge) has no schema support.
|
||||
|
||||
### 2.4 Phase ordering hides risk behind plumbing
|
||||
The risky work — scraping, planner algorithm, email/approval — is queued *after* full backend, full schema, and most of the React UI. Each is most likely to force schema changes; deferring them maximizes rework. **Action:** spike scrape-one-page and email-and-receive-approval-click *before* committing to the 12-table schema.
|
||||
|
||||
### 2.5 Shopping-list math is not implementable from current docs
|
||||
Unit conversion, recipe scaling, pantry subtraction, "1 bunch cilantro", "to taste salt" — all hand-waved. **Action:** define canonical units, conversion tables, rounding rules, and ambiguous-quantity handling before pantry/recipe models freeze.
|
||||
|
||||
### 2.6 Environment variables inconsistent across `.env.example`, ORIENTATION, RUNNING, plan
|
||||
**Action:** one canonical env-var table with name / required / optional / default / scope (backend startup vs frontend build vs optional integration). Add startup validation.
|
||||
|
||||
### 2.7 Accessibility absent despite family-facing UI + email
|
||||
User's global CLAUDE.md mandates WCAG 2.1 AA for UI. **Action:** state the WCAG target in SPEC; cover web UI plus email (accessible HTML + plain-text alt).
|
||||
|
||||
### 2.8 Health check will fail under SQLAlchemy 2
|
||||
`db.execute("SELECT 1")` → use `text("SELECT 1")`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Single-Reviewer Findings worth keeping
|
||||
|
||||
### From Claude
|
||||
- **Silence-as-consent.** "If both approve OR no response → confirmed" combined with the >80% approval-rate metric means the metric is gamed by spam-foldering. State the default explicitly; replace the metric with one that's queryable.
|
||||
- **APScheduler + multi-worker uvicorn** will fire weekly scrape N times if N>1. Pin `--workers 1` and document, or move scheduling to a dedicated container.
|
||||
- **Self-hosting framing is undermined** by SendGrid, AI image API, Let's Encrypt, scraped images, optional Datadog. Either enumerate every external dependency in SPEC §8, or drop the "self-hosted, no external services" framing.
|
||||
- **Image strategy contradicts itself.** SPEC lists "AI image generation as primary source" as a non-goal; ARCHITECTURE/plan build it as fallback. Tighten language. Also: "Embed via CDN URL or inline base64" hides a real decision (base64 inflates ~33%, GMail filters >102KB).
|
||||
- **`spice_level` column with no spec mention** — dead schema or missing spec.
|
||||
- **`meal-planner-plan.md`** described as "may be superseded" in ORIENTATION. Either delete or freeze with a banner.
|
||||
- **Self-referential broken link:** `implementation-plan.md:431` references `docs/implemenation-plan.md` (typo).
|
||||
- **`SECRET_KEY` placeholder** gets copied into real `.env` files unchanged more often than anyone admits — make startup require it in non-dev.
|
||||
|
||||
### From GPT-5.5 (docs)
|
||||
- **Data retention windows** lack job owner / schedule / FK-cascade design. "Archive to JSON, delete rows" for meal plans can break feedback FKs.
|
||||
- **Observability is aspirational.** No log schema, correlation IDs, error taxonomy, alert thresholds. Scraper/email failures are core product risks — should be MVP, not future polish.
|
||||
- **Tech stack drift:** "CRA or Vite" wording in plan; project uses Vite. Remove the CRA option to avoid divergent setup.
|
||||
|
||||
### From GPT-5.5 (repo)
|
||||
- **Trailing-slash inconsistency.** Routers prefixed `/api/profile` with paths `/`, exposing `/api/profile/` rather than `/api/profile`. Decide canonical style and update routes + docs together.
|
||||
- **All API routes are static `{"message": ...}` placeholders** — no schemas, no validation, no DB I/O. Implement one resource end-to-end (probably profile or ingredients) to establish the pattern before fanning out.
|
||||
- **No quality gates exist:** no tests, no lint/format config wired to scripts, no CI yaml. Docs mention pytest/Black/isort/ESLint/Prettier — config and scripts are incomplete.
|
||||
|
||||
---
|
||||
|
||||
## 4. Where Reviewers Disagreed
|
||||
|
||||
Almost no direct contradictions. Two soft tensions:
|
||||
|
||||
1. **Severity of phase-status drift.** Claude rated this Minor (doc hygiene). GPT-5.5 rated it Blocking. Repo evidence (skeleton present but broken imports, missing files, no Alembic) supports **Blocking**. Treat as blocker.
|
||||
2. **Recipe-ingredient framing.** Claude framed as "pick one" abstractly. GPT-5.5 repo found the code already has both with a broken relationship — so the decision is not abstract; code must change either way.
|
||||
|
||||
---
|
||||
|
||||
## 5. Combined Remediation Order
|
||||
|
||||
Resolve in this order. Items 1–6 must land before Phase 2 schema work; 7–9 before any feature code.
|
||||
|
||||
1. **Decide household-vs-members model.** (§1.4) Unblocks per-voter approval, fixes the family-math inconsistency.
|
||||
2. **Pick recipe-ingredient representation.** (§1.1) Delete the loser from docs *and* code.
|
||||
3. **Define MVP auth.** (§1.2) Pick one model; put it in SPEC §8; scope `SECRET_KEY`.
|
||||
4. **Redesign approval flow.** (§1.3) GET → confirmation POST; per-voter token; TTL; single-use.
|
||||
5. **Reconcile phase status.** (§1.5) One canonical status doc reflecting actual code.
|
||||
6. **Stabilize the skeleton.** (§1.5) Fix model imports, broken `back_populates`, add `index.html` / `Pantry.tsx` / lockfile, fix `text("SELECT 1")`, configure Alembic, remove `create_all` from startup. Run the verification matrix below.
|
||||
7. **Schema cleanup pass.** (§1.6) Enums/CHECKs, `CITEXT`, `grocery_item.ingredient_id`, household-size CHECK, `GENERATED` total_time, day_of_week convention, drop or document `calorie_target` / `spice_level`.
|
||||
8. **Compose hardening.** (§1.7) Intra-network only for backend/frontend; nginx sole entrypoint.
|
||||
9. **Decide scheduler topology** before APScheduler is wired (§3 Claude). Separate container or pinned `--workers 1`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verification Matrix (currently failing — make this pass to claim Phase 1 complete)
|
||||
|
||||
```bash
|
||||
docker compose config
|
||||
docker compose build backend
|
||||
docker compose build frontend
|
||||
docker compose up -d db
|
||||
docker compose run --rm backend python -c "from app.main import app; print(app.title)"
|
||||
docker compose run --rm backend alembic upgrade head
|
||||
docker compose run --rm backend pytest
|
||||
docker compose run --rm frontend npm run build
|
||||
```
|
||||
|
||||
If any of these fail, Phase 1 is not complete regardless of what ORIENTATION.md says.
|
||||
|
||||
---
|
||||
|
||||
## 7. Additional Investigation Still Needed
|
||||
|
||||
No reviewer resolved these — they require concrete checks before proceeding:
|
||||
|
||||
- [ ] **Verify the Lucky URL** (`curl -I https://www.luckyncal.com` and confirm it serves the weekly-ad surface).
|
||||
- [ ] **Run the verification matrix** (§6) and capture which commands actually fail.
|
||||
- [ ] **Audit Lucky California ToS and recipe-site copyright** before the scraper exists.
|
||||
- [ ] **Confirm the chosen mail provider and its deliverability profile** to GMail/iCloud — silence-as-consent is dangerous if delivery rate <100%.
|
||||
- [ ] **Decide whether "self-hosted" framing is load-bearing.** If yes, enumerate every external dependency in SPEC §8 with 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.
|
||||
- [ ] **Define planner hard-vs-soft constraints, scoring weights, tie-breakers, and no-solution fallback.** Add example fixtures (one valid plan, one no-solution case).
|
||||
- [ ] **Define unit conversion / "1 bunch cilantro" / "to taste" rules** before pantry and shopping-list models freeze.
|
||||
- [ ] **Decide WCAG 2.1 AA scope** (web UI + accessible email HTML with plain-text alt).
|
||||
- [ ] **Define backup/restore procedure** for the postgres volume and any cached images.
|
||||
- [ ] **Define CI plan** — minimal smoke tests (backend import, mapper config, frontend build) before adding lint/format configs.
|
||||
|
||||
---
|
||||
|
||||
## 8. Suggested Response Format from Originating Agent
|
||||
|
||||
For each numbered item in §1 and §2, please respond with one of:
|
||||
|
||||
- **Accept + plan:** acknowledge, summarize the chosen resolution, and identify which doc/file will change.
|
||||
- **Reject + rationale:** explain why the finding does not apply, citing primary-source evidence (file:line or external doc).
|
||||
- **Defer + condition:** explain what blocks immediate resolution and what condition triggers it.
|
||||
|
||||
For §3 (single-reviewer findings) and §7 (open investigations), a checkbox-level acknowledgement is sufficient unless the agent disagrees.
|
||||
|
||||
The goal of this loop is to converge on one canonical version of SPEC, ARCHITECTURE, database-schema, and implementation-plan before regenerating the Phase 2 migration.
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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 1–6 must land before Phase 2 schema work; 7–9 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.
|
||||
Reference in New Issue
Block a user