Rewrite Plan 1 to drop Docker: venv + systemd + apt Postgres
Task 1 now scaffolds a Python venv and a systemd unit template instead of a Dockerfile/Compose stack. Task 3 installs Postgres directly via apt and creates dev/test databases on the same instance. All test-run commands switched from `docker compose run` to plain `pytest` with exported env vars.
This commit is contained in:
@@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
**Goal:** Stand up the deployable skeleton of Quantumancy — repo scaffold, Docker Compose, Postgres, and username/password auth with server-side session cookies — so every later plan (LLM pipeline, spirit modes, Codex) has a real, tested backend to build on.
|
**Goal:** Stand up the deployable skeleton of Quantumancy — repo scaffold, a local Postgres instance, and username/password auth with server-side session cookies — so every later plan (LLM pipeline, spirit modes, Codex) has a real, tested backend to build on.
|
||||||
|
|
||||||
**Architecture:** Python + FastAPI backend (async SQLAlchemy 2.0 / asyncpg against Postgres), served from a single `app` Docker container alongside a `postgres` container, plus an isolated `postgres_test` container for tests. Tables are created via `Base.metadata.create_all` at startup (no Alembic yet — schema is not stable enough to justify migration tooling at this stage; revisit once the schema stabilizes past Plan 3).
|
**Architecture:** Python + FastAPI backend (async SQLAlchemy 2.0 / asyncpg against Postgres), running from a Python venv under `uvicorn` on this Proxmox LXC CT — no containers. Postgres is installed directly via `apt` on the same CT, with two databases on the one instance: `quantumancy` (dev/prod) and `quantumancy_test` (test-only, dropped and recreated on every test run). Tables are created via `Base.metadata.create_all` at startup (no Alembic yet — schema is not stable enough to justify migration tooling at this stage; revisit once the schema stabilizes past Plan 3). A systemd unit template is included for running the app as a real service, though enabling it is a manual final step outside the automated task/test loop.
|
||||||
|
|
||||||
**Tech Stack:** FastAPI, SQLAlchemy 2.0 (async, asyncpg), argon2-cffi, httpx, pytest + pytest-asyncio, Docker Compose.
|
**Tech Stack:** FastAPI, SQLAlchemy 2.0 (async, asyncpg), argon2-cffi, httpx, pytest + pytest-asyncio, systemd.
|
||||||
|
|
||||||
## Global Constraints
|
## Global Constraints
|
||||||
|
|
||||||
- Backend is Python + FastAPI (spec §2).
|
- Backend is Python + FastAPI (spec §2).
|
||||||
- App serves plain HTTP on port 7777; TLS is handled entirely outside this repo by a Cloudflare Tunnel (spec §2).
|
- App serves plain HTTP on port 7777; TLS is handled entirely outside this repo by a Cloudflare Tunnel (spec §2).
|
||||||
|
- **No containers.** The app runs from a Python venv under `uvicorn`, managed by systemd; Postgres is installed directly via `apt` (spec §2, §9). This is a hard constraint from the user, not a stylistic preference — do not introduce Docker/Compose anywhere in this plan or its implementation.
|
||||||
- Database is Postgres (spec §2, §5).
|
- Database is Postgres (spec §2, §5).
|
||||||
- Auth is username/password with argon2 hashing, server-side session cookies (not JWT), and **open registration** — no invite gating (spec §5).
|
- Auth is username/password with argon2 hashing, server-side session cookies (not JWT), and **open registration** — no invite gating (spec §5).
|
||||||
- Per-account and per-IP rate limiting applies to all LLM-triggering endpoints (spec §5) — this plan builds the reusable limiter; wiring it onto LLM endpoints happens in Plan 2 once those endpoints exist.
|
- Per-account and per-IP rate limiting applies to all LLM-triggering endpoints (spec §5) — this plan builds the reusable limiter; wiring it onto LLM endpoints happens in Plan 2 once those endpoints exist.
|
||||||
@@ -30,21 +31,29 @@ This is 1 of 7 plans implementing the Quantumancy website spec (`docs/superpower
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 1: Repo Scaffold, Docker Compose, Health Check
|
## Task 1: Repo Scaffold, Venv, Health Check
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `backend/requirements.txt`
|
- Create: `backend/requirements.txt`
|
||||||
|
- Create: `backend/app/__init__.py`
|
||||||
- Create: `backend/app/main.py`
|
- Create: `backend/app/main.py`
|
||||||
- Create: `backend/Dockerfile`
|
|
||||||
- Create: `backend/pytest.ini`
|
- Create: `backend/pytest.ini`
|
||||||
- Create: `docker-compose.yml`
|
- Create: `deploy/quantumancy.service`
|
||||||
- Create: `.env.example`
|
- Create: `.env.example`
|
||||||
|
- Create: `.gitignore`
|
||||||
- Test: `backend/tests/test_health.py`
|
- Test: `backend/tests/test_health.py`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Produces: `app` FastAPI instance in `backend/app/main.py`, importable as `app.main:app`.
|
- Produces: `app` FastAPI instance in `backend/app/main.py`, importable as `app.main:app`.
|
||||||
|
|
||||||
- [ ] **Step 1: Create the infra files**
|
- [ ] **Step 1: Create the venv and infra files**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
`backend/requirements.txt`:
|
`backend/requirements.txt`:
|
||||||
```
|
```
|
||||||
@@ -60,64 +69,52 @@ pytest==8.3.3
|
|||||||
pytest-asyncio==0.24.0
|
pytest-asyncio==0.24.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Run: `pip install -r requirements.txt`
|
||||||
|
|
||||||
`backend/pytest.ini`:
|
`backend/pytest.ini`:
|
||||||
```ini
|
```ini
|
||||||
[pytest]
|
[pytest]
|
||||||
asyncio_mode = auto
|
asyncio_mode = auto
|
||||||
```
|
```
|
||||||
|
|
||||||
`backend/Dockerfile`:
|
`.gitignore` (repo root):
|
||||||
```dockerfile
|
```
|
||||||
FROM python:3.12-slim
|
backend/venv/
|
||||||
WORKDIR /app
|
__pycache__/
|
||||||
COPY requirements.txt .
|
*.pyc
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
.env
|
||||||
COPY app ./app
|
frontend/node_modules/
|
||||||
EXPOSE 7777
|
frontend/dist/
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7777"]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`docker-compose.yml`:
|
`.env.example` (repo root):
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
build: ./backend
|
|
||||||
ports:
|
|
||||||
- "7777:7777"
|
|
||||||
env_file: .env
|
|
||||||
depends_on:
|
|
||||||
- postgres
|
|
||||||
|
|
||||||
postgres:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: quantumancy
|
|
||||||
POSTGRES_PASSWORD: quantumancy
|
|
||||||
POSTGRES_DB: quantumancy
|
|
||||||
volumes:
|
|
||||||
- pgdata:/var/lib/postgresql/data
|
|
||||||
|
|
||||||
postgres_test:
|
|
||||||
image: postgres:16
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: quantumancy
|
|
||||||
POSTGRES_PASSWORD: quantumancy
|
|
||||||
POSTGRES_DB: quantumancy_test
|
|
||||||
tmpfs:
|
|
||||||
- /var/lib/postgresql/data
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pgdata:
|
|
||||||
```
|
```
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy
|
||||||
`.env.example`:
|
|
||||||
```
|
|
||||||
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres:5432/quantumancy
|
|
||||||
OLLAMA_BASE_URL=http://10.30.20.107:11434
|
OLLAMA_BASE_URL=http://10.30.20.107:11434
|
||||||
SESSION_SECRET=change-me-to-a-random-64-char-string
|
SESSION_SECRET=change-me-to-a-random-64-char-string
|
||||||
PORT=7777
|
PORT=7777
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`deploy/quantumancy.service`:
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Quantumancy web app
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/root/quantumancy/backend
|
||||||
|
EnvironmentFile=/root/quantumancy/.env
|
||||||
|
ExecStart=/root/quantumancy/backend/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 7777
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=3
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `deploy/quantumancy.service` is not enabled in this task — that's a manual deployment step (`sudo cp deploy/quantumancy.service /etc/systemd/system/ && sudo systemctl enable --now quantumancy`) to run once the app is further along. It's created now so the repo carries its own deployment config from day one.
|
||||||
|
|
||||||
- [ ] **Step 2: Write the failing test**
|
- [ ] **Step 2: Write the failing test**
|
||||||
|
|
||||||
`backend/tests/test_health.py`:
|
`backend/tests/test_health.py`:
|
||||||
@@ -136,7 +133,7 @@ def test_healthz_returns_ok():
|
|||||||
|
|
||||||
- [ ] **Step 3: Run test to verify it fails**
|
- [ ] **Step 3: Run test to verify it fails**
|
||||||
|
|
||||||
Run: `cd backend && python -m pytest tests/test_health.py -v`
|
Run: `cd backend && source venv/bin/activate && python -m pytest tests/test_health.py -v`
|
||||||
Expected: FAIL with `ModuleNotFoundError: No module named 'app'` (or `ImportError`) since `app/main.py` doesn't exist yet.
|
Expected: FAIL with `ModuleNotFoundError: No module named 'app'` (or `ImportError`) since `app/main.py` doesn't exist yet.
|
||||||
|
|
||||||
- [ ] **Step 4: Write minimal implementation**
|
- [ ] **Step 4: Write minimal implementation**
|
||||||
@@ -157,14 +154,14 @@ async def healthz():
|
|||||||
|
|
||||||
- [ ] **Step 5: Run test to verify it passes**
|
- [ ] **Step 5: Run test to verify it passes**
|
||||||
|
|
||||||
Run: `cd backend && python -m pytest tests/test_health.py -v`
|
Run: `cd backend && source venv/bin/activate && python -m pytest tests/test_health.py -v`
|
||||||
Expected: PASS
|
Expected: PASS
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add backend docker-compose.yml .env.example
|
git add backend/requirements.txt backend/app backend/pytest.ini backend/tests/test_health.py deploy/quantumancy.service .env.example .gitignore
|
||||||
git commit -m "chore: scaffold backend, docker compose, health check"
|
git commit -m "chore: scaffold backend venv, health check, systemd unit"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -199,7 +196,7 @@ def test_settings_load_from_env(monkeypatch):
|
|||||||
|
|
||||||
- [ ] **Step 2: Run test to verify it fails**
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
Run: `cd backend && python -m pytest tests/test_config.py -v`
|
Run: `cd backend && source venv/bin/activate && python -m pytest tests/test_config.py -v`
|
||||||
Expected: FAIL with `ModuleNotFoundError: No module named 'app.config'`
|
Expected: FAIL with `ModuleNotFoundError: No module named 'app.config'`
|
||||||
|
|
||||||
- [ ] **Step 3: Write minimal implementation**
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
@@ -241,9 +238,11 @@ async def get_db() -> AsyncSession:
|
|||||||
yield session
|
yield session
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: `Settings()` (module-level, in `app.config`) requires `DATABASE_URL`/`OLLAMA_BASE_URL`/`SESSION_SECRET` to be set in the environment or `.env` at import time. Task 3 introduces the real Postgres instance those values point to; for this task, `test_config.py` only exercises `Settings` directly with explicit env vars and never imports `app.db`, so no live database is required yet.
|
||||||
|
|
||||||
- [ ] **Step 4: Run test to verify it passes**
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
Run: `cd backend && python -m pytest tests/test_config.py -v`
|
Run: `cd backend && source venv/bin/activate && python -m pytest tests/test_config.py -v`
|
||||||
Expected: PASS
|
Expected: PASS
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
@@ -272,7 +271,24 @@ git commit -m "feat: add settings and async db engine"
|
|||||||
- Consumes: `Base`, `get_db` from `app.db` (Task 2).
|
- Consumes: `Base`, `get_db` from `app.db` (Task 2).
|
||||||
- Produces: `User` model (`id: uuid.UUID`, `username: str`, `password_hash: str`, `email: str | None`, `created_at: datetime`) in `app.models.user`; `hash_password(password: str) -> str` and `verify_password(password: str, password_hash: str) -> bool` in `app.security`; `RegisterRequest`, `UserOut` in `app.schemas`; `POST /auth/register` route. `conftest.py`'s `client` fixture and `_reset_db` fixture are reused by every later test file.
|
- Produces: `User` model (`id: uuid.UUID`, `username: str`, `password_hash: str`, `email: str | None`, `created_at: datetime`) in `app.models.user`; `hash_password(password: str) -> str` and `verify_password(password: str, password_hash: str) -> bool` in `app.security`; `RegisterRequest`, `UserOut` in `app.schemas`; `POST /auth/register` route. `conftest.py`'s `client` fixture and `_reset_db` fixture are reused by every later test file.
|
||||||
|
|
||||||
- [ ] **Step 1: Write the test fixtures and failing tests**
|
- [ ] **Step 1: Install Postgres and create the dev + test databases**
|
||||||
|
|
||||||
|
This is a one-time environment setup step for this CT (skip if already done):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update && sudo apt-get install -y postgresql
|
||||||
|
sudo -u postgres psql -c "CREATE ROLE quantumancy WITH LOGIN PASSWORD 'quantumancy';"
|
||||||
|
sudo -u postgres psql -c "CREATE DATABASE quantumancy OWNER quantumancy;"
|
||||||
|
sudo -u postgres psql -c "CREATE DATABASE quantumancy_test OWNER quantumancy;"
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify: `PGPASSWORD=quantumancy psql -h localhost -U quantumancy -d quantumancy_test -c '\conninfo'` should connect without error.
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env` in the repo root (`cp .env.example .env`) if not already present — `app.config.Settings` reads it via `env_file=".env"`, and `backend/app` runs with the repo root as its working directory when started via the systemd unit or `uvicorn` from the repo root. For running tests from inside `backend/`, export `DATABASE_URL` directly instead (see Step 2's run command) rather than relying on the `.env` file's relative path.
|
||||||
|
|
||||||
|
**`quantumancy_test` is dropped and recreated by every test run (see `conftest.py` below). Never point `DATABASE_URL` at the `quantumancy_test` database from anything other than the test suite.**
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write the failing tests**
|
||||||
|
|
||||||
`backend/tests/conftest.py`:
|
`backend/tests/conftest.py`:
|
||||||
```python
|
```python
|
||||||
@@ -334,14 +350,21 @@ async def test_register_duplicate_username_rejected(client):
|
|||||||
assert response.status_code == 409
|
assert response.status_code == 409
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Run test to verify it fails**
|
- [ ] **Step 3: Run test to verify it fails**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_auth.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_auth.py -v
|
||||||
|
```
|
||||||
Expected: FAIL — `ModuleNotFoundError: No module named 'app.models'` (or similar import error)
|
Expected: FAIL — `ModuleNotFoundError: No module named 'app.models'` (or similar import error)
|
||||||
|
|
||||||
Note: this `docker compose run` invocation against `postgres_test` (not `postgres`) is the standard way to run the test suite for the rest of this plan and all later plans — it points the app container at the isolated, tmpfs-backed test database so tests never touch real dev/prod data.
|
This exact `DATABASE_URL=... OLLAMA_BASE_URL=... SESSION_SECRET=... python -m pytest ...` invocation (pointed at `quantumancy_test`) is the standard way to run the test suite for the rest of this plan and all later plans.
|
||||||
|
|
||||||
- [ ] **Step 3: Write minimal implementation**
|
- [ ] **Step 4: Write minimal implementation**
|
||||||
|
|
||||||
`backend/app/models/user.py`:
|
`backend/app/models/user.py`:
|
||||||
```python
|
```python
|
||||||
@@ -473,12 +496,19 @@ async def healthz():
|
|||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Run test to verify it passes**
|
- [ ] **Step 5: Run test to verify it passes**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_auth.py tests/test_health.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_auth.py tests/test_health.py -v
|
||||||
|
```
|
||||||
Expected: PASS (all tests, including the Task 1 health check, which still needs to pass since `main.py` was rewritten)
|
Expected: PASS (all tests, including the Task 1 health check, which still needs to pass since `main.py` was rewritten)
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add backend/app/models backend/app/security.py backend/app/schemas.py backend/app/routes backend/app/main.py backend/tests/conftest.py backend/tests/test_auth.py
|
git add backend/app/models backend/app/security.py backend/app/schemas.py backend/app/routes backend/app/main.py backend/tests/conftest.py backend/tests/test_auth.py
|
||||||
@@ -532,7 +562,14 @@ async def test_me_without_cookie_rejected(client):
|
|||||||
|
|
||||||
- [ ] **Step 2: Run test to verify it fails**
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_auth.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_auth.py -v
|
||||||
|
```
|
||||||
Expected: FAIL — `404 Not Found` for `/auth/login` and `/auth/me` (routes don't exist yet)
|
Expected: FAIL — `404 Not Found` for `/auth/login` and `/auth/me` (routes don't exist yet)
|
||||||
|
|
||||||
- [ ] **Step 3: Write minimal implementation**
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
@@ -694,7 +731,14 @@ async def me(user: User = Depends(get_current_user)):
|
|||||||
|
|
||||||
- [ ] **Step 4: Run test to verify it passes**
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_auth.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_auth.py -v
|
||||||
|
```
|
||||||
Expected: PASS
|
Expected: PASS
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 5: Commit**
|
||||||
@@ -740,7 +784,14 @@ def test_different_keys_tracked_independently():
|
|||||||
|
|
||||||
- [ ] **Step 2: Run test to verify it fails**
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_rate_limit.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_rate_limit.py -v
|
||||||
|
```
|
||||||
Expected: FAIL with `ModuleNotFoundError: No module named 'app.rate_limit'`
|
Expected: FAIL with `ModuleNotFoundError: No module named 'app.rate_limit'`
|
||||||
|
|
||||||
- [ ] **Step 3: Write minimal implementation**
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
@@ -773,12 +824,26 @@ class RateLimiter:
|
|||||||
|
|
||||||
- [ ] **Step 4: Run test to verify it passes**
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest tests/test_rate_limit.py -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest tests/test_rate_limit.py -v
|
||||||
|
```
|
||||||
Expected: PASS
|
Expected: PASS
|
||||||
|
|
||||||
- [ ] **Step 5: Run the full test suite**
|
- [ ] **Step 5: Run the full test suite**
|
||||||
|
|
||||||
Run: `docker compose run --rm -e DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@postgres_test:5432/quantumancy_test app python -m pytest -v`
|
Run:
|
||||||
|
```bash
|
||||||
|
cd backend && source venv/bin/activate
|
||||||
|
DATABASE_URL=postgresql+asyncpg://quantumancy:quantumancy@localhost:5432/quantumancy_test \
|
||||||
|
OLLAMA_BASE_URL=http://10.30.20.107:11434 \
|
||||||
|
SESSION_SECRET=test-secret \
|
||||||
|
python -m pytest -v
|
||||||
|
```
|
||||||
Expected: PASS (all tests from Tasks 1-5)
|
Expected: PASS (all tests from Tasks 1-5)
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [ ] **Step 6: Commit**
|
||||||
@@ -792,9 +857,9 @@ git commit -m "feat: add rate limiter utility"
|
|||||||
|
|
||||||
## Self-Review
|
## Self-Review
|
||||||
|
|
||||||
**Spec coverage:** repo/Docker scaffold (§2, §9) → Task 1. Postgres (§2) → Task 1/2. FastAPI backend (§2) → all tasks. Username/password + argon2 + open registration (§5) → Task 3. Server-side session cookies (§5) → Task 4. Per-account/per-IP rate limiting (§5) → Task 5 (utility only; enforcement on LLM endpoints is explicitly deferred to Plan 2, since those endpoints don't exist until then). Everything else in the spec (the four modes, Codex, TTS/i18n, deployment finalization) is out of scope for Plan 1 by design — covered in Plans 2-7.
|
**Spec coverage:** repo scaffold (§2, §9) → Task 1. Postgres (§2), no-container/venv+systemd deployment (§2, §9) → Task 1 (systemd unit) and Task 3 (Postgres install). FastAPI backend (§2) → all tasks. Username/password + argon2 + open registration (§5) → Task 3. Server-side session cookies (§5) → Task 4. Per-account/per-IP rate limiting (§5) → Task 5 (utility only; enforcement on LLM endpoints is explicitly deferred to Plan 2, since those endpoints don't exist until then). Everything else in the spec (the four modes, Codex, TTS/i18n) is out of scope for Plan 1 by design — covered in Plans 2-7.
|
||||||
|
|
||||||
**Placeholder scan:** none found — every step has complete, runnable code.
|
**Placeholder scan:** none found — every step has complete, runnable code or exact commands.
|
||||||
|
|
||||||
**Type consistency:** `User.id: uuid.UUID` matches `UserOut.id: uuid.UUID`. `generate_session_token() -> tuple[str, str]` return order (raw, hash) matches its usage in `routes/auth.py`'s `login` (`raw_token, token_hash = generate_session_token()`). `hash_token(raw: str) -> str` used identically in both `auth_session.py` (internally) and `deps.py`. `get_current_user` return type `User` matches its use as `user: User = Depends(get_current_user)` in `routes/auth.py` and is the exact name/import path (`app.deps.get_current_user`) later plans will depend on.
|
**Type consistency:** `User.id: uuid.UUID` matches `UserOut.id: uuid.UUID`. `generate_session_token() -> tuple[str, str]` return order (raw, hash) matches its usage in `routes/auth.py`'s `login` (`raw_token, token_hash = generate_session_token()`). `hash_token(raw: str) -> str` used identically in both `auth_session.py` (internally) and `deps.py`. `get_current_user` return type `User` matches its use as `user: User = Depends(get_current_user)` in `routes/auth.py` and is the exact name/import path (`app.deps.get_current_user`) later plans will depend on.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user