diff --git a/plan/00-OVERVIEW.md b/plan/00-OVERVIEW.md index f4713a9..146a94d 100644 --- a/plan/00-OVERVIEW.md +++ b/plan/00-OVERVIEW.md @@ -72,3 +72,31 @@ player input 15, embedding-based retrieval of relevant memories into context. Each phase ends with the app runnable and testable end-to-end. + +## Public release (phases 7–10) + +Goal: public GitHub repo + hosted multi-user deployment, linkable from resume/website. + +| Area | Decision | +|---|---| +| License | MIT | +| Auth | Email + password, **optional** — guest sessions play instantly, register to keep data | +| Signup | Open (rate-limited) | +| LLM keys | BYOK per user + shared server-funded demo key (capped, details TBD in Phase 8) | +| Database (hosted) | **TBD — ask at start of Phase 9** (SQLite-on-disk vs Postgres) | +| Hosting | Render (tier TBD in Phase 10) | +| Domain | Platform URL (custom domain later, optional) | +| README media | Deferred — text-only first, screenshots/GIF in a later pass | + +Open questions are recorded at the top of each phase file under "Ask before implementing". + +7. **[Phase 7 — Public repo & portability](07-phase-public-repo.md)**: MIT license, portfolio + README, Dockerfile + compose, cross-platform run instructions, publish to GitHub. +8. **[Phase 8 — Optional accounts & multi-user](08-phase-accounts.md)** *(the big one)*: + guest-first sessions, optional email+password upgrade, per-user data scoping across all + routers/tables, per-user encrypted BYOK settings, shared demo key with caps. +9. **[Phase 9 — Production hardening](09-phase-hardening.md)**: env-var config, quickjs + time/memory limits, rate limiting, size/row caps, locked-down debug surface, production + serving, database decision. +10. **[Phase 10 — Deploy & publish](10-phase-deploy.md)**: Render blueprint + deploy, seeded + demo scenarios, live smoke test, resume/website links and blurb. diff --git a/plan/07-phase-public-repo.md b/plan/07-phase-public-repo.md new file mode 100644 index 0000000..73c952a --- /dev/null +++ b/plan/07-phase-public-repo.md @@ -0,0 +1,53 @@ +# Phase 7 — Public repo & portability + +**Goal:** make the repo public-worthy and runnable by anyone on any OS, so the GitHub link is +immediately usable on a resume — before any hosted-deployment work. + +## Decisions (confirmed) + +| Question | Answer | +|---|---| +| License | **MIT** | +| README media (screenshots/GIF) | **Skip for now** — text-only README; visuals in a later pass | + +**Ask before implementing:** GitHub repo name (default suggestion: `ai-dnd`) and whether the +existing local commit history/message is fine to publish as-is. + +## Repo hygiene + +- [ ] Add `LICENSE` (MIT, current year, Parth Thakkar). +- [ ] Verify no secrets or user data are tracked (`openrouter_key.env`, `data.db` — already + gitignored and never committed; re-verify before push). +- [ ] Add `backend/.env.example` documenting every env var the app reads (grows in Phase 9). +- [ ] Decide what to do with `CODE_REVIEW_FINDINGS.md` and `plan/` — keep (shows process, good + for a portfolio) — just give them a one-line mention in the README. + +## README rewrite (portfolio-grade, text-only) + +- [ ] Pitch paragraph: what it is, what makes it interesting (AI Dungeon-compatible scripting, + memory bank with embeddings, full prompt transparency/Insights, provider-agnostic). +- [ ] Feature list with pointers into the code (scripting engine, context builder, memory bank). +- [ ] Architecture diagram (reuse/refresh the one in `plan/00-OVERVIEW.md`). +- [ ] Setup instructions for **Windows (start.ps1), macOS/Linux (manual), and Docker**. +- [ ] "Bring your own model" section: Ollama / LM Studio / OpenRouter free models — emphasize it + runs fully free. +- [ ] Placeholder section for screenshots/GIF (added in a later pass). + +## Docker (one-command run for non-Windows users) + +- [ ] `Dockerfile`: multi-stage — build frontend (`npm run build`), then Python image serving + FastAPI with the built SPA mounted (SPA fallback already exists in `app/main.py`). +- [ ] `docker-compose.yml`: single service, volume for `data.db`, port mapping. +- [ ] `start.sh` for macOS/Linux dev parity with `start.ps1` (optional, nice-to-have). +- [ ] Test: `docker compose up` from a clean clone → app works at `http://localhost:8000`. + +## Publish + +- [ ] Create public GitHub repo (`gh repo create`), push `main`. +- [ ] Add repo description, topics (`ai-dungeon`, `fastapi`, `react`, `llm`, `interactive-fiction`). +- [ ] Confirm the GitHub rendering of README looks right. + +## Exit criteria + +A stranger on macOS with Docker installed can clone the repo, run one command, open the app, +paste an OpenRouter free-tier key, and play an adventure — without asking you anything. diff --git a/plan/08-phase-accounts.md b/plan/08-phase-accounts.md new file mode 100644 index 0000000..f7f8f92 --- /dev/null +++ b/plan/08-phase-accounts.md @@ -0,0 +1,71 @@ +# Phase 8 — Optional accounts & multi-user + +**Goal:** turn the single-user app into a multi-user one where **accounts are optional**: +a visitor can start playing instantly as a guest, and can register (email + password) at any +point to keep their adventures across devices/browsers. This is the largest phase — it touches +every router and most tables. + +## Decisions (confirmed) + +| Question | Answer | +|---|---| +| Auth method | **Email + password, optional** — guest sessions work without an account | +| Signup policy | **Open signup** (rate-limited) | +| LLM API keys | **BYOK + shared demo key** — users can paste their own key; users without one get limited turns on a server-funded key | + +**Ask before implementing:** +- Demo-key specifics: which provider/key funds it, model whitelist (free models only?), + per-user turn/day cap, what the "out of demo turns" message says. +- Guest data retention: how long before unclaimed guest data is deleted (suggestion: 30 days + of inactivity). +- Password reset: skip for v1, or implement email-based reset (requires an email provider)? + +## Data model + +- [ ] `User` table: id, email (nullable — null means guest), password_hash (nullable), + created_at, last_seen_at, is_guest flag (derivable from email; keep explicit for clarity). +- [ ] Add `user_id` FK to: `Adventure`, `Scenario`, `Script`, `Settings` (and anything else + global today — audit `models.py`). Story cards/actions inherit scope via their parent. +- [ ] Settings becomes **per-user** (endpoint URL, API key, models, memory-bank config). + API key **encrypted at rest** (Fernet with a server-side `SECRET_KEY` env var). +- [ ] Migration (in `migrations.py` style): create a "local user", assign all existing rows to + it — a fresh clone/local install keeps working exactly as before. +- [ ] Demo/starter scenarios: mark as `user_id = NULL` + `is_public` so everyone sees them + (decide exact mechanism when implementing; seed via `seed_demo.py`). + +## Auth & sessions + +- [ ] Guest flow: first API call with no session → create guest User, set a signed, long-lived + httpOnly session cookie. No signup wall anywhere. +- [ ] Register: email + password (hashed with bcrypt/argon2) **upgrades the current guest user + in place** — same user_id, data automatically kept. +- [ ] Login: standard session issue; logging in from a fresh guest session with existing account + discards the empty guest (or merges — ask if guest has data). +- [ ] Session middleware/dependency: every router handler resolves `current_user`; **every query + filtered by `user_id`** (this is the bulk of the diff — go router by router). +- [ ] Rate limits on register/login endpoints (brute-force protection). +- [ ] Local/self-hosted mode stays frictionless: single auto-created local user, no login UI + unless `MULTI_USER=true` (env var) — resume demo runs multi-user, local clones don't care. + +## Shared demo key (BYOK fallback) + +- [ ] Server env vars: `DEMO_API_KEY`, `DEMO_ENDPOINT_URL`, `DEMO_MODEL_WHITELIST`, + `DEMO_TURNS_PER_DAY`. +- [ ] If a user has no API key configured: use demo key, restrict model picker to the whitelist, + count turns per user per day, friendly error + "add your own key in Settings" when capped. +- [ ] Turn counting includes memory-bank background calls (or disable memory bank on demo key — + decide when implementing). + +## Frontend + +- [ ] Auth UI: register/login modal or page, "Save your progress" nudge for guests (subtle, + e.g. in the header), logout, account menu. +- [ ] `api.js`: send cookies (`credentials: include`), handle 401 → re-establish guest session. +- [ ] Settings page: per-user; show demo-key status ("Using shared demo key — N turns left today"). + +## Exit criteria + +Two different browsers hit the deployed app: each gets its own guest world (adventures invisible +to the other), both can play immediately on the demo key. One registers mid-adventure and its +data survives; logging in from the other browser shows the same account data. Local +`start.ps1` / `docker compose up` still works with zero auth friction. diff --git a/plan/09-phase-hardening.md b/plan/09-phase-hardening.md new file mode 100644 index 0000000..3323de6 --- /dev/null +++ b/plan/09-phase-hardening.md @@ -0,0 +1,55 @@ +# Phase 9 — Production hardening + +**Goal:** make the app safe and stable to expose to strangers on the internet: config via +environment, resource limits on everything user-controlled, and a single-service production +build. + +## Decisions + +| Question | Answer | +|---|---| +| Database | **Decide at start of this phase.** SQLite on a persistent disk (zero code change, but Render disks require the ~$7/mo starter tier) vs Postgres (free/cheap managed options, better resume talking point, needs SQLAlchemy URL + migration tweaks). Revisit with current Render pricing. | + +**Ask before implementing:** the database choice above, and target monthly budget (drives +Render tier: free tier sleeps after idle + has no persistent disk). + +## Configuration + +- [ ] All config via env vars with sane local defaults: `DATABASE_URL`, `SECRET_KEY` + (sessions + API-key encryption), `MULTI_USER`, `CORS_ORIGINS`, demo-key vars (Phase 8), + port/host. Document each in `backend/.env.example`. +- [ ] Fail fast on missing `SECRET_KEY` when `MULTI_USER=true`. + +## Abuse & resource limits + +- [ ] **quickjs limits**: per-execution time limit and memory limit on the scripting engine + (`scripting/engine.py`) — user-submitted JS must not be able to hang or OOM the server. +- [ ] Rate limiting on expensive endpoints (turn generation, script run, auth) — per-user and + per-IP (e.g. `slowapi`). +- [ ] Request size limits (script source length, memory/story-card text lengths, action text). +- [ ] Cap per-user row counts (adventures, scenarios, scripts, story cards) with friendly errors. +- [ ] Audit debug router (`routers/debug.py`) and `/docs`: admin-only or disabled when + `MULTI_USER=true` — debug log may contain other users' prompts. + +## Production serving + +- [ ] Single service: FastAPI serves the built SPA (fallback already exists) — verify the Docker + image from Phase 7 is production-ready (no `--reload`, multiple workers or async-safe + single worker; check SQLite + multiple workers interaction before choosing). +- [ ] CORS locked to the deployed origin (moot if same-origin single service — verify). +- [ ] Security headers middleware; cookies `Secure` + `SameSite`. +- [ ] Streaming (SSE) works behind Render's proxy — verify no buffering issues. +- [ ] Structured logging; scrub API keys from all logs and the debug page. + +## Database (after decision) + +- [ ] If Postgres: swap `DATABASE_URL`, verify JSON-blob columns (embeddings) and + `migrations.py` work; test full play loop. +- [ ] If SQLite-on-disk: confirm WAL mode + single-worker (or serialized writes) is acceptable. +- [ ] Backup story: platform DB backups (Postgres) or a scheduled dump of the disk (SQLite). + +## Exit criteria + +Running the production Docker image locally with `MULTI_USER=true`: a hostile user cannot hang +the server with a `while(true)` script, cannot see another user's data or the debug log, gets +rate-limited instead of burning the demo key, and the app streams turns normally the whole time. diff --git a/plan/10-phase-deploy.md b/plan/10-phase-deploy.md new file mode 100644 index 0000000..0e27375 --- /dev/null +++ b/plan/10-phase-deploy.md @@ -0,0 +1,46 @@ +# Phase 10 — Deploy & publish + +**Goal:** the app live on Render at a public URL, linked from resume/website alongside the +GitHub repo. + +## Decisions (confirmed) + +| Question | Answer | +|---|---| +| Platform | **Render** | +| Domain | **Platform URL is fine** (e.g. `ai-dnd.onrender.com`); custom domain can be added later anytime | + +**Ask before implementing:** Render tier (free-with-sleep vs ~$7/mo always-on — depends on the +Phase 9 database decision), and the exact service name (it becomes the public URL). + +## Deploy + +- [ ] `render.yaml` blueprint: web service from the Dockerfile, env vars (SECRET_KEY generated, + demo-key vars, `MULTI_USER=true`), health check endpoint, plus disk or managed Postgres + per the Phase 9 decision. +- [ ] Set up the Render service, connect the GitHub repo, auto-deploy on push to `main`. +- [ ] Seed production with 2–3 good demo scenarios (public/starter scenarios from Phase 8) so + first-time visitors have something great to click immediately. +- [ ] Smoke test the live URL: guest play on demo key, register, BYOK flow, scripting, memory + bank, Insights — from a device/network that isn't yours. +- [ ] Free-tier note: if on free tier, first request after idle takes ~30–60s to wake — add a + friendly loading state or accept it (revisit tier if it feels bad). + +## Post-launch guardrails + +- [ ] Watch demo-key spend/usage for the first days (OpenRouter dashboard); confirm caps hold. +- [ ] Set up uptime monitoring (free: UptimeRobot or similar) — optional. +- [ ] Error visibility: Render logs are enough for v1; note how to pull them. + +## Resume / website + +- [ ] README: add the live-demo link + "Try it" section at the top. +- [ ] 2–3 sentence project blurb for resume/website (stack, the interesting hard parts: + AI Dungeon-compatible JS scripting sandbox, embedding-based memory bank, prompt + transparency, guest-first optional auth). +- [ ] Later pass (deferred from Phase 7): screenshots/demo GIF for README and website card. + +## Exit criteria + +A recruiter clicks one link on your resume, lands on the live app, plays three turns of a demo +scenario as a guest without configuring anything, and can find the GitHub repo from the page.