# garden2 — Garden v2 (rebuild of /srv/apps/garden)

Full rebuild of the garden app per the approved plan (see the plan file
referenced below). FastAPI `app/{routers,services,models}` + React PWA +
PostgreSQL(pgvector), following /srv/projects/standards/ (coding.md,
frontend.md, react.md).

## Status
P0 foundations in progress. v1 (garden.riverway.ca) remains the daily app
until cutover; this project serves garden2.riverway.ca during the build.

## Project info
- Type: custom (FastAPI + Postgres sidecar in Docker)
- Port: 3121 (app binds 172.17.0.1:3121; db has NO host port — internal only)
- Domain: garden2.riverway.ca (cutover later flips garden.riverway.ca here)
- Created: 2026-07-03

## Layout
- `backend/app/` — factory-only main.py; routers/ services/ models/ schemas/
- `backend/alembic/` — ONE initial revision = full v2 schema; every change after
  is its own revision. NEVER hand-written ALTERs.
- `frontend/` — Vite React PWA (P0.6+); TS client GENERATED from OpenAPI
- `scripts/` — export_v1.py, media_manifest.py, diff_harness.py, migrate_v1.py,
  mint_grant.py (dev grant shim)
- `data/v1-snapshot/` — frozen v1 snapshot (the migration oracle's left side)
  + v1_schema_pragma.txt (exact v1 schema reference)

## Managing the container
Use `srv-gw` commands (NOT docker/podman directly):
```bash
srv-gw deploy --project garden2 --build   # build + recreate (required: real build step)
srv-gw status --project garden2
srv-gw logs --project garden2
srv-gw restart --project garden2          # does NOT reload .env; use deploy for that
```

## Working rules (binding)
- Auth is Pattern B: X-Auth-Grant header verified in app/auth.py; DEFAULT-DENY
  middleware; the public allowlist lives in auth.py and is walked by
  tests/test_auth.py. NEVER add broad path exemptions.
- Dev API calls: `curl -H "X-Auth-Grant: $(python3 scripts/mint_grant.py --user rian)" ...`
- Secrets in .env (600). Verify by shape only. Never echo values.
- Sync `def` for DB routes; `async def` ONLY for AI/SSE routes.
- Every mutation writes activity_log via services/audit.py.
- Every change bumps backend/app/version.py + a changelog line.
- Tests: `cd backend && .venv/bin/python -m pytest` — test_auth.py must stay
  green (walks every route for default-deny).
- v1 is READ-ONLY reference: never modify /srv/apps/garden from this project.

## Migration oracle
`scripts/diff_harness.py` compares the v2 Postgres export against
`data/v1-snapshot/snapshot_v1.json`. Zero diffs on all 9 invariants = the
cutover gate. Cutover night re-freezes v1 and re-runs everything.
V2-only columns are allowlisted in `scripts/canonical.py` V2_ONLY_COLUMNS —
extend it whenever a schema revision adds a column to a CARRIED table.

## App feedback loop (acceptance testing)
Rian files app feedback via the capture sheet's "App feedback" tab (voice or
text; the SPA route is auto-recorded). It lands in `field_notes` with
kind='feedback' + page_context, status='new' — excluded from the diary,
threads, and the notes mirror at the query level. To read the queue:
`GET /api/notes?status=all&kind=feedback` (or Library > Captures > "app
feedback" tab in the UI). Process items by fixing what they describe, then
PATCH the capture to status='processed' so the queue stays clean. Feedback
with audio: transcribe via the existing captures transcribe endpoint first.
CUTOVER NOTE: feedback rows are v2-native — harvest before the final
migrate --wipe and replay after (task #9 checklist has the details).

## Plan
Approved plan: /home/rian/.claude/plans/fancy-sniffing-minsky.md (Context,
locked decisions, schema, milestones P0-P6, parity addenda).
