phase: 168_admin_settings_panel
**Phase 168 — Admin Settings panel — final verification: PASS** 1. **Implemented/verified:** `/settings.html` (admin-only) with the 75-field catalogue (`app/core/settings_catalog.py`), DB-over-env overrides in Postgres (`app_settings`, migration 0028 at head, `settings_overlay.py` resolver, `app/api/admin_settings.py` — 4 admin-only routes), live-apply on save with no restart + restart-required badges, Reset/Clear all, write-only secrets, `.env`-never-written, `BOR_SETTINGS_DB=0` kill switch. 2. **Phase E2E: 12/12** — run with `E2E_APP_PORT=8200` (ports 8123/8124 are held by an unrelated "undercooked" project; no process killed, no code change — the documented env override). 3. **Regression set: all 7 suites green in isolation** (theme tab, API tokens, configurable brand, global tuning, admin auth, cache busting, wide desktop column — 38 tests). 4. **Full suite: unit+integration exit 0 (4066 passed), app/ coverage 99%** (gate >90%); **ruff clean, pyright 0 errors / 0 warnings.** 5. **Log contract (rule 10) verified live:** per-turn + per-response byte-budget lines present; settings write lines carry key + action, **never the value**. 6. **Byte-budget audit: all endpoints under the 262,144 B budget**; settings catalogue 23,513 B (gzip 3,326). 7. **Defect fixed (one):** criterion 5 had no integration pin — added `test_bad_row_inserted_directly_never_bricks_boot` to `tests/integration/test_admin_settings_api.py` (malformed row inserted directly → fresh `create_app()` boots, row skipped with logged reason, rest of table applies, `/api/health` 200) — 12/12 green. 8. **Docs pass:** `README.md` and `AGENTS.md` reviewed in full — both consistent with the as-built feature (Settings page table, Settings section, Configuration-reference note, `.env.example` block); **no changes required**. 9. **Cleanup:** audit app killed (by PID); `app_settings` left empty (0 rows); all changes left in the working tree (no commit, per protocol). 10. **Completion criteria:** all 7 checkboxes verified green (criterion 7's commit is the harness's). 11. **Next pending phase:** `170_edit_bubble_sizing`.
This commit is contained in:
+37
-1
@@ -20,7 +20,9 @@ from fastapi.staticfiles import StaticFiles
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
from starlette.responses import FileResponse
|
||||
|
||||
from app.api.admin_settings import router as admin_settings_router
|
||||
from app.api.auth import router as auth_router
|
||||
from app.api.chat import reset_runtime_caches
|
||||
from app.api.chat import router as chat_router
|
||||
from app.api.chat_images import router as chat_images_router
|
||||
from app.api.chats import (
|
||||
@@ -42,7 +44,7 @@ from app.api.suggestions import router as suggestions_router
|
||||
from app.api.sync import router as sync_router
|
||||
from app.api.tokens import router as tokens_router
|
||||
from app.api.ui_settings import router as ui_settings_router
|
||||
from app.config import get_settings
|
||||
from app.config import get_settings, invalidate_settings
|
||||
from app.core.auth import ensure_admin_configured
|
||||
from app.core.caching import configure_caching
|
||||
from app.core.compression import SelectiveGZipMiddleware
|
||||
@@ -50,6 +52,8 @@ from app.core.debugging import configure_debugging
|
||||
from app.core.logging import configure_logging
|
||||
from app.core.response_budget import ResponseBudgetMiddleware
|
||||
from app.core.security_headers import SecurityHeadersMiddleware
|
||||
from app.core.settings_overlay import enabled as settings_db_enabled
|
||||
from app.core.settings_overlay import load_overrides, register_invalidator
|
||||
|
||||
configure_logging()
|
||||
configure_debugging()
|
||||
@@ -97,6 +101,33 @@ def create_app() -> FastAPI:
|
||||
# variable(s) — the app never starts in a half-authenticated state.
|
||||
ensure_admin_configured(settings)
|
||||
|
||||
# Phase 168 (task 02, D2/D3): prime the DB-over-env resolver now
|
||||
# that auth is verified. The module-level ``settings`` above stays
|
||||
# the pre-boot value the middleware wiring below uses (LOCKED A4:
|
||||
# those boot-wired settings apply on the NEXT boot); every consumer
|
||||
# that calls get_settings() from here on resolves DB-over-env — no
|
||||
# restart. invalidate_settings() runs the runtime reset hooks
|
||||
# (task 03) and clears the cache; a boot-time DB miss is fail-soft
|
||||
# (one WARNING, env values in effect) and the next resolution
|
||||
# retries, so the prime can never cache an env-only view forever.
|
||||
invalidate_settings()
|
||||
if settings_db_enabled():
|
||||
get_settings()
|
||||
logger.info("settings overrides loaded count=%d", len(load_overrides()))
|
||||
else:
|
||||
get_settings()
|
||||
logger.info("settings overrides loaded source=env-only disabled")
|
||||
|
||||
# Phase 168 (task 03): the settings panel's Save must retune the
|
||||
# running process, and app/core must never import app/api (PLAN §3's
|
||||
# direction) — so create_app(), the composition root, wires the
|
||||
# reset hook. It runs on every invalidate_settings() after the cache
|
||||
# clear: the next chat turn rebuilds the shared LLMClient and the
|
||||
# concurrency semaphore from the CURRENT settings (no restart), while
|
||||
# in-flight turns keep the client they started with (A12 semantics).
|
||||
register_invalidator(reset_runtime_caches)
|
||||
logger.info("settings invalidator registered name=chat_runtime_caches")
|
||||
|
||||
app = FastAPI(title=settings.app_name, version=settings.app_version)
|
||||
|
||||
# Signed single-admin session cookie (Starlette middleware, itsdangerous
|
||||
@@ -134,6 +165,10 @@ def create_app() -> FastAPI:
|
||||
# single ui_settings row — the Theme tab's persistence) — admin-only
|
||||
# (router-wide require_admin; anonymous AND token users stay 403).
|
||||
app.include_router(ui_settings_router, prefix="/api")
|
||||
# Phase 168 (task 04): the admin settings panel surface (read/write
|
||||
# the app_settings overrides — DB-over-env, live on Save) — admin-only
|
||||
# (router-wide require_admin; a token user stays 403).
|
||||
app.include_router(admin_settings_router, prefix="/api")
|
||||
# Phase 51: the anonymous shared-chat read — NO admin dependency.
|
||||
# /api/shared/<token> is the JSON snapshot; /shared/<token> (the
|
||||
# page route below, registered without a prefix) is the page.
|
||||
@@ -197,6 +232,7 @@ def create_app() -> FastAPI:
|
||||
"/history.html",
|
||||
"/tokens.html", # phase 79 task 06: the Tokens view
|
||||
"/theme.html", # phase 91 task 04: the Theme view (shell route)
|
||||
"/settings.html", # phase 168 task 05: the Settings view (shell route)
|
||||
),
|
||||
)
|
||||
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
|
||||
|
||||
Reference in New Issue
Block a user