Files
brain-of-reese/app/core/settings_catalog.py
T
ducoterra f20d40e4fe
Build and Push Containers / build-and-push-app (push) Successful in 1m44s
Build and Push Containers / build-and-push-db (push) Successful in 12s
phase: 188_chunks_hnsw_index
**Phase 188 — final verification pass: all criteria green.**

- **Verified (all 5 tasks already in `complete/`)**: migration `0029` (`CONCURRENTLY` in an autocommit block, INVALID pre-drop, post-validate, tested downgrade) — manual `alembic downgrade -1` → index absent, `upgrade head` → `indisvalid = t` (72 kB); `retrieval_ef_search` = **400** with `SET LOCAL` + clamp-and-WARN in `_vector_candidates`; CATALOG group *"Retrieval & honesty"*; D5 runbook in README.
- `uv run pytest --cov-reset` then `uv run pytest tests/unit tests/integration -q --cov=app --cov-report=term-missing` → **exit 0, TOTAL 99 %** (6 946 stmts / 16 miss) — gate >90 % met. (A stale `.coverage` left by an earlier session produced a spurious 90.00 %/3-failure reading; `--cov-reset` cleared it.)
- `uv run pytest tests/e2e/test_vector_index_recall.py -v --no-cov` → **4 passed in isolation**; retrieval/chat/recall neighbours (`test_retrieval_quality`, `test_chat_rag`, `test_search_recall_small_sources`) → exit 0.
- `uv run ruff check .` → **All checks passed**; `uv run pyright` (whole repo) → **0 errors**.
- **Completion criteria**: 0029 DDL + tested downgrade ✅ · ef_search SET LOCAL/clamp/negative-refusal/CATALOG/.env.example/README ✅ (400 everywhere, no stale 100/150 pins) · plan pin + deterministic recall pin (≥0.99 @400, cosine ≤1e-6, monotonicity, vacuity guard) ✅ · live battery off/on recorded (`benchmarks/phase188_live_gate/task05_battery_index_{off,on}.txt` byte-identical, `deterministic_gate_recall.txt`, build 161 s / 1 625 MB / 81 MB RSS, phase report `.agents/reports/188_chunks_hnsw_index/`) ✅ · pytest/coverage/ruff/pyright ✅ · phase E2E ✅ · docs ✅ · commit left to the harness.
- **Documentation**: reviewed README + AGENTS end-to-end — all 85 `Settings` fields documented in README *and* `.env.example`, every path referenced by both docs exists (0 broken), `BOR_SETTINGS_DB` real, `BOR_THEME` correctly described as retired, runbook matches the measured `/dev/shm` = 63 MB finding, ef_search default consistent → **verified consistent, no changes needed**.
- **Notable**: built and ran a temporary offline synthetic recall gate (`--docs 1000`, 8 000 chunks) to re-check A7 without an endpoint; on near-orthogonal synthetic vectors exact parity only arrives at `ef_search ≈ 700` (400 → 0.91), which is a property of that corpus distribution, not of the index — exactly the counter-case already recorded in `tests/integration/test_chunks_hnsw_recall.py`. Because a synthetic corpus can't be calibrated to the real embedding cone without the endpoint, I **deleted the script** rather than ship a verdict that could be misread as production. `AIPI_KEY` is absent in this environment (and `100.117.2.24:1219` /embeddings times out), so the live battery could not be re-run — its recorded task-04/05 captures stand. Scratch DB `brain_of_reese_hnswgate` dropped; repo left clean.
- **Next pending phase**: `189_chat_save_long_turns`.
2026-10-17 03:18:23 -04:00

378 lines
14 KiB
Python

"""The admin Settings panel's catalogue (phase 168, task 02).
``TODO.md`` L3: "I want an admin panel that can edit anything in .env
and save it to the database."
This module is the single source of truth for what the panel shows and
what the DB-over-env overlay (``app.core.settings_overlay``) is allowed
to do with each of the ``Settings`` fields:
* :data:`CATALOG` maps every ``Settings`` field name to its
presentation metadata (``group`` + ``label``) and its capability
flags:
- ``secret`` — write-only (LOCKED A4): the API reports ``has_value``
(and ``masked=True`` / ``value=None``), never the plaintext; a
saved value is masked on read.
- ``restart_required`` — boot-wired (LOCKED A4): the middleware /
engine / logging / static mount read it once at boot, so the
override applies on the NEXT boot (the panel shows the badge).
- ``overridable`` — ``False`` for LOCKED A3's ``database_url``
(it builds the very engine that would read the override — a
chicken-and-egg brick) and ``embedding_dim`` (A6 fixes
``chunks.embedding`` at table-creation; a dim override would
corrupt retrieval).
The panel (task 05) is GENERATED from this catalogue — never
hand-written — so a new ``Settings`` field appears there only once
it has a :class:`SettingMeta`; the completeness pin
(``tests/unit/test_settings_catalog.py``: ``set(CATALOG) ==
set(Settings.model_fields)``) fails the build the moment a field is
added without metadata, so it can never silently disappear from the
panel.
* :data:`GROUPS` is the display order of the groups — it mirrors
``app/config.py``'s own section comments (App, HTTP response
compression / byte budget, UI customization, Database, LLM, Chat
history, RAG tuning, …).
* :data:`SECRETS`, :data:`RESTART_REQUIRED` and
:data:`NON_OVERRIDABLE` are DERIVED from :data:`CATALOG` — never
re-listed by hand, so they cannot drift.
:func:`env_var` keeps the ``BOR_`` prefix a pure presentation concern
(D1): the stored key is the bare ``Settings`` field name
(``relevance_threshold``), ``"BOR_" + key.upper()`` is what the panel
and the API show, and a stored prefix could double up — so it is
derived, never stored.
"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class SettingMeta:
"""One settings field's panel metadata + capability flags."""
key: str
group: str
label: str
#: Write-only (LOCKED A4) — masked in responses, ``is_set`` only.
secret: bool = False
#: Boot-wired (LOCKED A4) — applies on the next boot.
restart_required: bool = False
#: ``False`` = read-only row (LOCKED A3).
overridable: bool = True
#: The ``BOR_`` prefix is presentation only (D1) — see :func:`env_var`.
ENV_PREFIX = "BOR_"
#: Display order of the groups (mirrors ``app/config.py``'s section
#: comments); :data:`CATALOG` is keyed in the same order.
GROUPS: tuple[str, ...] = (
"App & branding",
"HTTP & logging",
"Database",
"LLM (aipi)",
"Chat history",
"Retrieval & honesty",
"Chunking & embedding",
"Prompt budgets",
"Agent tools",
"Import & sources",
"Images",
"PDF",
"Docs push",
"Admin & security",
)
#: Every ``Settings`` field, in :data:`GROUPS` display order. Completeness
#: is pinned by ``tests/unit/test_settings_catalog.py`` (the diff is in
#: the failure message — a new setting without metadata fails the build).
CATALOG: dict[str, SettingMeta] = {
# --- App & branding ---
"app_name": SettingMeta("app_name", "App & branding", "App name"),
"app_version": SettingMeta("app_version", "App & branding", "App version"),
"environment": SettingMeta("environment", "App & branding", "Environment"),
"input_placeholder": SettingMeta(
"input_placeholder", "App & branding", "Input placeholder"
),
"footer_text": SettingMeta("footer_text", "App & branding", "Footer text"),
"suggestions": SettingMeta(
"suggestions", "App & branding", "Suggestions (JSON array)"
),
# --- HTTP & logging ---
"log_level": SettingMeta(
"log_level", "HTTP & logging", "Log level", restart_required=True
),
"log_large_response_bytes": SettingMeta(
"log_large_response_bytes",
"HTTP & logging",
"Log large response bytes",
),
"compress_min_bytes": SettingMeta(
"compress_min_bytes", "HTTP & logging", "Compress min bytes",
restart_required=True,
),
"static_dir": SettingMeta(
"static_dir", "HTTP & logging", "Static dir", restart_required=True
),
# --- Database ---
"database_url": SettingMeta(
"database_url", "Database", "Database URL",
secret=True, overridable=False,
),
"db_pool_size": SettingMeta(
"db_pool_size", "Database", "DB pool size", restart_required=True
),
"db_pool_max_overflow": SettingMeta(
"db_pool_max_overflow", "Database", "DB pool max overflow",
restart_required=True,
),
"db_pool_recycle": SettingMeta(
"db_pool_recycle", "Database", "DB pool recycle", restart_required=True
),
"chat_max_concurrent": SettingMeta(
"chat_max_concurrent", "Database", "Chat max concurrent"
),
# --- LLM (aipi) ---
"llm_base_url": SettingMeta(
"llm_base_url", "LLM (aipi)", "LLM base URL"
),
"llm_api_key": SettingMeta(
"llm_api_key", "LLM (aipi)", "LLM API key", secret=True
),
"llm_chat_model": SettingMeta(
"llm_chat_model", "LLM (aipi)", "LLM chat model"
),
"llm_embed_model": SettingMeta(
"llm_embed_model", "LLM (aipi)", "LLM embed model"
),
"llm_summary_model": SettingMeta(
"llm_summary_model", "LLM (aipi)", "LLM summary model"
),
"llm_chat_thinking_level": SettingMeta(
"llm_chat_thinking_level", "LLM (aipi)", "Chat thinking level"
),
"llm_summary_thinking_level": SettingMeta(
"llm_summary_thinking_level", "LLM (aipi)", "Summary thinking level"
),
"stream_thinking": SettingMeta(
"stream_thinking", "LLM (aipi)", "Stream thinking"
),
"llm_retries": SettingMeta("llm_retries", "LLM (aipi)", "LLM retries"),
"llm_retry_delay": SettingMeta(
"llm_retry_delay", "LLM (aipi)", "LLM retry delay"
),
"llm_timeout": SettingMeta("llm_timeout", "LLM (aipi)", "LLM timeout (s)"),
# --- Chat history ---
"history_max_turns": SettingMeta(
"history_max_turns", "Chat history", "History max turns"
),
"history_max_chars": SettingMeta(
"history_max_chars", "Chat history", "History max chars"
),
# --- Retrieval & honesty ---
"embedding_dim": SettingMeta(
"embedding_dim", "Retrieval & honesty", "Embedding dim",
overridable=False,
),
"top_n_docs": SettingMeta("top_n_docs", "Retrieval & honesty", "Top N docs"),
"relevance_threshold": SettingMeta(
"relevance_threshold", "Retrieval & honesty", "Relevance threshold"
),
"lexical_support_floor": SettingMeta(
"lexical_support_floor", "Retrieval & honesty", "Lexical support floor"
),
"source_usefulness_floor": SettingMeta(
"source_usefulness_floor",
"Retrieval & honesty",
"Source usefulness floor",
),
"related_max_docs": SettingMeta(
"related_max_docs", "Retrieval & honesty", "Related max docs"
),
"suggested_docs": SettingMeta(
"suggested_docs", "Retrieval & honesty", "Suggested docs"
),
"suggestion_preview_chars": SettingMeta(
"suggestion_preview_chars",
"Retrieval & honesty",
"Suggestion preview chars",
),
"hybrid_vector_candidates": SettingMeta(
"hybrid_vector_candidates",
"Retrieval & honesty",
"Hybrid vector candidates",
),
"retrieval_ef_search": SettingMeta(
"retrieval_ef_search", "Retrieval & honesty", "Vector ef_search"
),
"hybrid_lexical_candidates": SettingMeta(
"hybrid_lexical_candidates",
"Retrieval & honesty",
"Hybrid lexical candidates",
),
"rrf_k": SettingMeta("rrf_k", "Retrieval & honesty", "RRF k"),
"recency_boost": SettingMeta(
"recency_boost", "Retrieval & honesty", "Recency boost"
),
"recency_half_life_days": SettingMeta(
"recency_half_life_days",
"Retrieval & honesty",
"Recency half-life days",
),
"name_hit_bonus": SettingMeta(
"name_hit_bonus", "Retrieval & honesty", "Name hit bonus"
),
# --- Chunking & embedding ---
"chunk_target_chars": SettingMeta(
"chunk_target_chars", "Chunking & embedding", "Chunk target chars"
),
"chunk_overlap_chars": SettingMeta(
"chunk_overlap_chars", "Chunking & embedding", "Chunk overlap chars"
),
"embed_batch_size": SettingMeta(
"embed_batch_size", "Chunking & embedding", "Embed batch size"
),
"embed_question_max_chars": SettingMeta(
"embed_question_max_chars",
"Chunking & embedding",
"Embed question max chars",
),
# --- Prompt budgets ---
"max_output_tokens": SettingMeta(
"max_output_tokens", "Prompt budgets", "Max output tokens"
),
"steering_max_chars": SettingMeta(
"steering_max_chars", "Prompt budgets", "Steering max chars"
),
"summary_max_chars": SettingMeta(
"summary_max_chars", "Prompt budgets", "Summary max chars"
),
"kb_overview_max_chars": SettingMeta(
"kb_overview_max_chars", "Prompt budgets", "KB overview max chars"
),
"overview_input_max_chars": SettingMeta(
"overview_input_max_chars",
"Prompt budgets",
"Overview input max chars",
),
"folder_summary_input_max_chars": SettingMeta(
"folder_summary_input_max_chars",
"Prompt budgets",
"Folder summary input max chars",
),
# --- Agent tools ---
"agent_max_rounds": SettingMeta(
"agent_max_rounds", "Agent tools", "Agent max rounds"
),
"read_max_chars": SettingMeta(
"read_max_chars", "Agent tools", "Read max chars"
),
"answer_text": SettingMeta("answer_text", "Agent tools", "Answer text"),
"show_text_max_chars": SettingMeta(
"show_text_max_chars", "Agent tools", "Show text max chars"
),
"show_text_max_blocks": SettingMeta(
"show_text_max_blocks", "Agent tools", "Show text max blocks"
),
# --- Import & sources ---
"import_extensions": SettingMeta(
"import_extensions", "Import & sources", "Import extensions (CSV)"
),
"git_sources": SettingMeta(
"git_sources", "Import & sources", "Git sources (CSV)"
),
"sources_dir": SettingMeta(
"sources_dir", "Import & sources", "Sources dir"
),
"upload_dir": SettingMeta("upload_dir", "Import & sources", "Upload dir"),
"upload_max_mb": SettingMeta(
"upload_max_mb", "Import & sources", "Upload max MB"
),
"sync_session_reset_files": SettingMeta(
"sync_session_reset_files", "Import & sources", "Sync session reset files"
),
"sync_prune_batch": SettingMeta(
"sync_prune_batch", "Import & sources", "Sync prune batch"
),
# --- Images ---
"images": SettingMeta("images", "Images", "Images"),
"image_extensions": SettingMeta(
"image_extensions", "Images", "Image extensions (CSV)"
),
"image_dir": SettingMeta("image_dir", "Images", "Image dir"),
"chat_image_dir": SettingMeta(
"chat_image_dir", "Images", "Chat image dir"
),
"chat_image_max_mb": SettingMeta(
"chat_image_max_mb", "Images", "Chat image max MB"
),
"answer_images": SettingMeta("answer_images", "Images", "Answer images"),
# --- PDF ---
"pdf_pictorial": SettingMeta("pdf_pictorial", "PDF", "PDF pictorial"),
"pdf_pictorial_min_chars": SettingMeta(
"pdf_pictorial_min_chars", "PDF", "PDF pictorial min chars"
),
"pdf_image_skip_blank": SettingMeta(
"pdf_image_skip_blank", "PDF", "PDF image skip blank"
),
"pdf_page_dir": SettingMeta("pdf_page_dir", "PDF", "PDF page dir"),
# --- Docs push ---
"docs_repo": SettingMeta("docs_repo", "Docs push", "Docs repo"),
"docs_user": SettingMeta("docs_user", "Docs push", "Docs user"),
"docs_token": SettingMeta(
"docs_token", "Docs push", "Docs token", secret=True
),
"docs_branch": SettingMeta("docs_branch", "Docs push", "Docs branch"),
"docs_base_branch": SettingMeta(
"docs_base_branch", "Docs push", "Docs base branch"
),
"docs_work_dir": SettingMeta("docs_work_dir", "Docs push", "Docs work dir"),
# --- Admin & security ---
"admin_password": SettingMeta(
"admin_password", "Admin & security", "Admin password", secret=True
),
"session_secret": SettingMeta(
"session_secret", "Admin & security", "Session secret",
secret=True, restart_required=True,
),
"session_max_age": SettingMeta(
"session_max_age", "Admin & security", "Session max age",
restart_required=True,
),
"session_cookie": SettingMeta(
"session_cookie", "Admin & security", "Session cookie",
restart_required=True,
),
}
#: The masked set (LOCKED A4: write-only; ``database_url`` is read-only
#: but its URL carries credentials, so it is masked too) — derived,
#: never re-listed by hand.
SECRETS: frozenset[str] = frozenset(
key for key, meta in CATALOG.items() if meta.secret
)
#: The boot-wired set (LOCKED A4: logging, the gzip middleware, the
#: static mount, ``create_engine``, ``SessionMiddleware``) — derived.
RESTART_REQUIRED: frozenset[str] = frozenset(
key for key, meta in CATALOG.items() if meta.restart_required
)
#: The read-only set (LOCKED A3) — derived.
NON_OVERRIDABLE: frozenset[str] = frozenset(
key for key, meta in CATALOG.items() if not meta.overridable
)
def env_var(key: str) -> str:
"""The env var for one bare setting key — presentation only (D1).
The stored key is the bare ``Settings`` field name; the ``BOR_``
form is what the panel and the API show, never what is stored
(a stored prefix could double up).
"""
return ENV_PREFIX + key.upper()