phase: 84_docs_push_error_sanitization
**Phase 84 — final verification pass: all green, no defects found** - Verified implementation: `app/core/errors.py` (verbatim lift of sync masker), `app/api/sync.py` alias import, docs-push 502 `detail=sanitize_error(str(exc))`, all five `llm.py` error sites sanitized; new/extended test pins in place - Tests: `uv run pytest` → **1714 passed, 0 failed**; targeted pins (new unit ×2 + integration ×1, existing 502 pin) → 13 passed; sync/git-sources regression → 67 passed - Coverage: `uv run pytest --cov=app --cov-report=term-missing` → **99%** (`app/core/errors.py` 100%, `app/rag/llm.py` 100%) — >90% met - E2E isolation: `uv run pytest tests/e2e/test_smoke.py -v --no-cov` → **3 passed** - Lint/types: `uv run ruff check .` → clean; `uv run pyright` → **0 errors** - Criteria: 502 masks `*****@`/never token + row untouched ✅; LLM base-URL masked, credential-free strings byte-identical ✅; `_CREDS_RE` only in `app/core/errors.py` (working-tree grep) ✅; full gate green ✅; `git diff --stat` limited to the 4 app files + 2 modified test files + 3 phase task files (untracked: new module, new unit test, complete/ dir, reports, audit plan) ✅ - Commit/phase move left to the harness per instructions (task files already in `complete/`) - No deviations; nothing to fix - Next pending phase: **85_mobile_menu_gate_overlap**
This commit is contained in:
+16
-6
@@ -29,6 +29,7 @@ from openai import AsyncOpenAI, AsyncStream
|
||||
from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam
|
||||
|
||||
from app.config import Settings, get_settings
|
||||
from app.core.errors import sanitize_error
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Phase 71: the filter type is only needed for typing (the module
|
||||
@@ -285,7 +286,11 @@ class LLMClient:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001 — wrap transport-level failures
|
||||
raise EmbeddingError(
|
||||
f"embeddings request to {self.settings.llm_base_url} failed: {e}"
|
||||
# Phase 84 (SEC-13): a base URL configured with
|
||||
# embedded ``user:pass@`` credentials must not reach
|
||||
# the error string — sanitized at construction.
|
||||
f"embeddings request to {sanitize_error(self.settings.llm_base_url)} "
|
||||
f"failed: {e}"
|
||||
) from e
|
||||
self.embed_batches += 1
|
||||
self._check_dims(vecs)
|
||||
@@ -324,18 +329,20 @@ class LLMClient:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001 — wrap transport-level failures
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} failed: {e}"
|
||||
# Phase 84 (SEC-13): sanitize the base URL (see embed).
|
||||
f"chat completion from {sanitize_error(self.settings.llm_base_url)} "
|
||||
f"failed: {e}"
|
||||
) from e
|
||||
if not resp.choices:
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} "
|
||||
f"chat completion from {sanitize_error(self.settings.llm_base_url)} "
|
||||
"returned no choices"
|
||||
)
|
||||
content = resp.choices[0].message.content
|
||||
if content is None or not content.strip():
|
||||
raise LLMError(
|
||||
f"chat completion from {self.settings.llm_base_url} returned "
|
||||
"empty content — refusing to store a silent summary"
|
||||
f"chat completion from {sanitize_error(self.settings.llm_base_url)} "
|
||||
"returned empty content — refusing to store a silent summary"
|
||||
)
|
||||
return content.strip()
|
||||
|
||||
@@ -483,7 +490,10 @@ class LLMClient:
|
||||
except LLMError:
|
||||
raise
|
||||
except Exception as e: # noqa: BLE001 — wrap transport-level failures
|
||||
raise LLMError(f"chat stream from {self.settings.llm_base_url} failed: {e}") from e
|
||||
# Phase 84 (SEC-13): sanitize the base URL (see embed).
|
||||
raise LLMError(
|
||||
f"chat stream from {sanitize_error(self.settings.llm_base_url)} failed: {e}"
|
||||
) from e
|
||||
finally:
|
||||
# Phase 48: deterministic teardown — whenever ``create()``
|
||||
# succeeded, close the endpoint's stream on every subsequent
|
||||
|
||||
Reference in New Issue
Block a user