Files
ai-rpg/CHANGELOG.md
2026-06-21 01:02:25 +03:00

144 lines
13 KiB
Markdown

# Changelog
All notable changes to AI-RPG are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.5] — 2026-06-20
### Fixed
- **app/main.py**: added `create_all_tables(engine)` call at the start of `lifespan`. Without this, the backend started but crashed on every DB query with `relation "settings" does not exist` because tables were never created in PostgreSQL. Tables are now created idempotently on every startup via `Base.metadata.create_all` ( SQLAlchemy skips tables that already exist).
- **app/main.py**: also added `seed_builtin_presets(session)` call so the 2 builtin presets (Classic Fantasy, Deep Space Outpost) are seeded on first startup, not just settings.
- **app/migrations/versions/**: renamed `001_initial_schema.py``_001_initial_schema.py`. Python module names cannot start with a digit, so `from app.migrations.versions.001_initial_schema import create_all_tables` was a SyntaxError. The leading underscore is a conventional marker for "internal" modules.
- **README.md**: updated references to the renamed migration file.
### Why this happened
The lifespan handler was supposed to run DB migrations as its first step, but I forgot to wire it up. The `seed_default_settings()` call immediately tried to `SELECT FROM settings` against a fresh PostgreSQL database with no tables. SQLAlchemy 2.x's `create_all` is idempotent (skips existing tables), so this is safe to call on every startup — equivalent to `alembic upgrade head` for our single-migration MVP.
## [1.0.4] — 2026-06-20
### Fixed (proper fix for CORS env parsing)
- **app/config.py**: replaced the broken `Annotated[list[str], NoDecode]` approach with a simpler, more robust one:
- `cors_origins` is now declared as a plain `str` (comma-separated, e.g. `"http://a,http://b"` or `"*"`).
- Added a `cors_origins_list` property that splits the string into a `list[str]` on demand.
- This sidesteps the entire `EnvSettingsSource.decode_complex_value` / JSON-parsing codepath — pydantic-settings sees a `str` field, takes the env value as-is, no JSON parsing attempted.
- **app/main.py**: updated `CORSMiddleware(allow_origins=cfg.cors_origins_list)` to use the new property.
- **Why v1.0.3 didn't work**: in pydantic-settings 2.7.0, `NoDecode` is importable but `_annotation_is_complex()` doesn't actually check for it (only checks for `Json`). The marker was added to the package surface but the inner logic was only wired up in a later version. Switching to a plain `str` field is the most reliable fix and works across all pydantic-settings 2.x versions.
- All 68 unit tests still pass.
## [1.0.3] — 2026-06-20
### Fixed
- **app/config.py**: fixed `SettingsError: error parsing value for field "cors_origins" from source "EnvSettingsSource"` that crashed the backend on startup when `CORS_ORIGINS` was set as a comma-separated string (e.g. `http://localhost:8080,http://localhost:5173,http://localhost`).
- Root cause: pydantic-settings v2 by default tries to JSON-parse complex-typed env vars before applying field validators. The comma-separated string isn't valid JSON, so parsing failed before our `@field_validator(mode="before")` could split it.
- Fix: declared `cors_origins: Annotated[list[str], NoDecode]``NoDecode` is a pydantic-settings marker that disables JSON pre-parsing, so the raw string reaches our `_split_cors` validator unchanged.
- **requirements.txt**: bumped `pydantic` 2.7.1 → 2.9.2 and `pydantic-settings` 2.2.1 → 2.7.0. The `NoDecode` annotation was only introduced in pydantic-settings 2.6+, so the older versions couldn't support the fix above. All 68 unit tests still pass with the new versions.
## [1.0.2] — 2026-06-20
### Fixed
- **requirements.txt**: added missing `email-validator==2.2.0` dependency. Pydantic's `EmailStr` type (used in `RegisterRequest`, `AdminRegisterRequest`, `LoginRequest.user.email`, `UserPublic.email`, `TokenResponse.user.email`) requires this package at runtime, but it's not bundled with pydantic itself. Without it the backend crashed at startup with `ImportError: email-validator is not installed, run pip install pydantic[email]`. Also removed a duplicate `httpx==0.27.0` line.
## [1.0.1] — 2026-06-20
### Fixed
- **docker-compose.yml**: removed obsolete `version: "3.9"` (caused warning in modern Docker Compose).
- **docker-compose.yml**: fixed frontend build context — was `./frontend` (broke `COPY deploy/nginx.conf` and `COPY frontend/package*.json` in Dockerfile.frontend). Now correctly `.` (project root) with `dockerfile: deploy/Dockerfile.frontend`.
- **docker-compose.yml**: fixed frontend port mapping — was `5173:5173` but the frontend container is nginx on port 80. Now `8080:80` so the app is accessible at `http://localhost:8080`.
- **docker-compose.yml**: added `extra_hosts: ["host.docker.internal:host-gateway"]` to the backend service — enables `LLM_API_URL=http://host.docker.internal:11434/v1` to work on Linux hosts (not just Docker Desktop on Mac/Windows).
- **docker-compose.yml**: added `VITE_API_BASE_URL: /api` build arg for the frontend service — bakes the relative `/api` URL into the Vite bundle so the browser uses the same origin + nginx proxies `/api``backend:8000`.
- **deploy/Dockerfile.frontend**: added `ARG VITE_API_BASE_URL=/api` + `ENV` so the build arg actually gets baked into the Vite bundle.
- **deploy/nginx.conf**: extended SSE timeouts from 300s → 600s; added `proxy_send_timeout`; added `Upgrade`/`Connection` headers for future WebSocket support; added gzip for static assets.
- **.env.example**: clarified `VITE_API_BASE_URL` — only used by local `npm run dev`, ignored by docker build (which uses `/api` relative). Removed the misleading `http://localhost/api` default.
- **frontend/src/lib/api.ts**: now respects `VITE_API_BASE_URL` env var with fallback to relative `/api`. Works both for local dev (point at separate backend) and Docker (nginx proxy).
- **frontend/src/vite-env.d.ts**: added Vite env type declarations so TypeScript knows about `import.meta.env.VITE_API_BASE_URL`.
- **app/api/deps.py**: added `?access_token=<jwt>` query parameter fallback for SSE endpoints. Native `EventSource` cannot send `Authorization` headers, so the frontend SSE client passes the token via query string. Without this fix, all SSE endpoints (`/iterate/stream`, `/builder/stream`, `/editor/stream`) returned 401.
- **.dockerignore**: added at project root — excludes `node_modules/`, `__pycache__/`, `.venv/`, `data/`, `.git/`, etc. from Docker build contexts (faster builds, smaller context transfer).
## [1.0.0] — 2026-06-20
### Added — Sprint 1: Foundation
- docker-compose with 4 services: db (PostgreSQL 15), qdrant (1.9), backend (FastAPI/uvicorn), frontend (Vite dev / nginx prod).
- SQLAlchemy 2.x async models for all 10 tables: `users`, `settings`, `world_presets`, `worlds`, `entities`, `steps`, `step_tool_calls`, `deferred_triggers`, `story_entries`, `llm_call_logs`.
- Alembic-equivalent idempotent migration `001_initial_schema.py` (creates all tables, no pgvector).
- `init_qdrant.py` creates collections `entities` and `story_entries` with payload indexes on `world_id`, `entity_type`, `entry_type`, `deleted`, `created_at`.
- Settings seed: 33 default keys (llm.*, embeddings.*, context.*, qdrant.*, game.*, ui.*, admin.setup_token).
- 2 builtin presets: Classic Fantasy, Deep Space Outpost.
- `GET /api/health` returns `{status, db, qdrant, llm, embeddings, version}`.
### Added — Sprint 2: Auth + Admin
- `POST /api/register` (only if at least one admin exists), `POST /api/register/admin?token=`, `POST /api/auth/login` (email or username), `POST /api/auth/refresh`, `POST /api/auth/logout`, `GET /api/auth/me`.
- JWT (access + refresh) with python-jose, password hashing with passlib[bcrypt].
- Password strength validation: ≥8 chars, ≥1 letter, ≥1 digit, blacklist of trivial passwords.
- Anti-enumeration: same `401 invalid_credentials` for "user not found" and "wrong password".
- Admin setup token: auto-generated on first startup, printed in logs.
- `GET/PATCH /api/admin/settings` with secret masking.
- `GET /api/admin/llm-logs` with filters (world_id, stage, status, pagination) + detail view.
- `GET /api/admin/users`, `PATCH /api/admin/users/{id}` (admin/active toggle).
- `GET /api/admin/stats` (users, worlds, steps, avg LLM latency).
- Diagnostic endpoints: `POST /api/admin/test/llm`, `/test/llm-tools`, `/test/embeddings`, `/test/embeddings/probe-dimension`, `/embeddings/recreate-collections`.
- `POST /api/admin/upload-icon` (multipart, favicon/logo/og_image, ≤1MB, PNG/SVG/JPG/WebP/ICO).
### Added — Sprint 3: World Builder
- `LlmClient` — OpenAI-compatible chat completions client with retry (3 attempts, exp backoff), streaming, tool-calling, separate-transaction logging.
- `MockLlmClient` — replay-based mock for tests/dev when no LLM configured.
- `app/prompts/` — 11 stage prompts in English (world_builder_schema/env/entities, world_editor, orchestrator_phase1/2/3_summary/3_suggest, intro_scene, subagent, summary).
- `POST /api/worlds` creates draft world + returns SSE URL.
- `GET /api/sessions/worlds/{id}/builder/stream` runs the 4-step builder flow: schemas → environment → entities (tool-calling loop) → intro scene (with submit_step + suggest_actions).
- If preset_id provided, schemas/environment copied from preset (skip first LLM call).
### Added — Sprint 4: World Editor
- `POST /api/worlds/{id}/edit` body `{instruction}` → SSE URL.
- `GET /api/sessions/worlds/{id}/editor/stream?instruction=` runs LLM with world_editor tools.
- `propose_changes` tool returns diff, `ask_user` emits clarification event, `comment_to_user` for chat.
- Optimistic locking via `updated_at` on `PATCH /api/worlds/{id}` (409 state_conflict on mismatch).
- `PATCH /api/worlds/{id}` for direct JSON edits.
- `DELETE /api/worlds/{id}` soft-delete (status='archived').
### Added — Sprint 5: Orchestrator
- `POST /api/sessions/worlds/{id}/iterate` body `{action, action_source}` creates new step + returns SSE URL.
- `GET /api/sessions/worlds/{id}/iterate/stream?step_id=` runs the 3-phase orchestrator:
- **Phase 1**: tool-calling loop with `submit_plan` terminator, max 8 substeps (configurable).
- **Phase 2**: writer LLM call with `submit_step`, streams `scene_chunk` events.
- **Phase 3**: persist + deferred triggers + summary (if history > threshold) + suggest_actions.
- `POST /api/sessions/worlds/{id}/retry` soft-deletes last step + creates new one with same action.
- `POST /api/sessions/worlds/{id}/rollback` soft-deletes last step.
- 17 game tools: entity_create/get/list/update/delete, env_update/get, update_plot_rails, advance_time, schedule_trigger, calc (with dice), random_choice (deterministic), rag_query/add, run_subagent, submit_plan, submit_step, suggest_actions.
- 4 schema tools: schema_add_type/add_field/remove_field/modify_field.
### Added — Sprint 6: Frontend polish
- React 18 + Vite 5 + TypeScript 5 + Tailwind CSS 3 + zustand 4 + react-i18next 14.
- Dark theme by default (Tailwind `dark:` class on `<html>`).
- 8 pages: Login, Register, AdminRegister, WorldsList, WorldBuilder, WorldEdit, Play, Admin.
- 23 components: 9 UI primitives, 5 session, 3 worlds, 6 admin.
- 5 zustand stores: auth, ui, worlds, session, toast.
- i18n bundles in English + Russian (complete).
- Native EventSource SSE client with reconnect + Last-Event-ID.
- Mobile-first responsive layout.
- TypeScript strict mode, 0 type errors, `npm run build` succeeds (340 KB JS / 22 KB CSS).
### Added — Sprint 7: RAG + Triggers + Summary + Context
- `app/core/rag.py` — two-stage retrieval: Qdrant vector search → PostgreSQL hydration.
- `HashEmbedder` (offline, deterministic) and `OpenAIEmbedder` (OpenAI-compatible API) implementations.
- Embedder provider selection via `embeddings.provider` setting (`offline_hash` | `openai`).
- Auto-fallback: `embeddings.api_url` falls back to `llm.api_url` if empty (and same for api_key).
- Embedder cache with `reset_embedder_cache()` (called on settings update).
- `rag_query` returns empty list on embedder/Qdrant failure (non-blocking).
- `rag_add` saves story entry with `embedding_status='pending'` if embedding fails (background indexer can retry).
- `index_entity` helper for entity vectors.
- World isolation via Qdrant payload filter `world_id`.
- `_cleanup_qdrant(world_id)` deletes all points for a world on world delete (best-effort).
- Deferred triggers: Phase 3.1 fires triggers where `fire_at <= current_time`, appends summary to scene_text, marks `is_fired=true`.
- Summary: Phase 3.2 generates summary when `len(recent_steps) > compression_threshold_messages`, stores as StoryEntry with `metadata.type=summary`.
- Context manager: builds messages with system prompt + optional summary + last N guaranteed messages + current action.
### Added — Sprint 8: Production
- `README.md` with quickstart, architecture diagram, API overview, testing instructions.
- `pytest.ini` + 65+ unit tests across 8 test files.
- `CHANGELOG.md` (this file).
- `docker-compose.yml` with healthchecks for db and qdrant.
- `deploy/Dockerfile.backend`, `deploy/Dockerfile.frontend`, `deploy/nginx.conf`.
- `.env.example` with all 24 env vars documented.
- `.gitignore` for Python, Node, env, IDE, data dirs.
- Frontend production build verified (`npm run build` produces `dist/`).