refactor(search): pre-push review fixes for PR #750

Address findings surfaced by `pre-push-review` after the round 8 sweep:

- Add deck verifier symmetry tests (404, transient 5xx, unexpected
  exception, non-numeric metadata) so deck has the same shape as the
  notes/news/files verifiers. Also add unexpected-exception tests for
  the news and file verifiers, which had `except Exception` branches
  no test was reaching. Keeps the registry-style verifier coverage
  uniform.
- Modernize sibling field types in `VectorSyncState`, `AppContext`,
  and `OAuthAppContext` from `Optional[X]` to `X | None`, matching the
  `eviction_task_group: TaskGroup | None` field added in the round 8
  diff (resolves the inconsistency flagged by A6). The lone remaining
  `Optional` import is dropped.
- Reverse cross-reference direction in the verifier docstrings: the
  later-defined `_verify_deck_cards` and `_verify_news_items` now
  point at `_verify_notes` as the canonical hoisted-cast pattern,
  rather than `_verify_notes` forward-referring to verifiers defined
  below it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-01 21:57:38 +02:00
co-authored by Claude Opus 4.7
parent 06fe3916e7
commit 3153c9dac4
3 changed files with 195 additions and 23 deletions
+17 -19
View File
@@ -9,7 +9,7 @@ import traceback
from collections.abc import AsyncIterator
from contextlib import AsyncExitStack, asynccontextmanager
from dataclasses import dataclass
from typing import Optional, cast
from typing import cast
from urllib.parse import urlparse
import anyio
@@ -316,10 +316,10 @@ class VectorSyncState:
and FastMCP session lifespans (where MCP tools need access to the streams).
"""
document_send_stream: Optional[MemoryObjectSendStream] = None
document_receive_stream: Optional[MemoryObjectReceiveStream] = None
shutdown_event: Optional[anyio.Event] = None
scanner_wake_event: Optional[anyio.Event] = None
document_send_stream: MemoryObjectSendStream | None = None
document_receive_stream: MemoryObjectReceiveStream | None = None
shutdown_event: anyio.Event | None = None
scanner_wake_event: anyio.Event | None = None
# Long-lived task group used for fire-and-forget background work spawned
# from the request path (e.g. ADR-019 verify-on-read eviction). Set by the
# starlette lifespan after entering its task group; cleared on shutdown.
@@ -335,11 +335,11 @@ class AppContext:
"""Application context for BasicAuth mode."""
client: NextcloudClient
storage: Optional["RefreshTokenStorage"] = None
document_send_stream: Optional[MemoryObjectSendStream] = None
document_receive_stream: Optional[MemoryObjectReceiveStream] = None
shutdown_event: Optional[anyio.Event] = None
scanner_wake_event: Optional[anyio.Event] = None
storage: "RefreshTokenStorage | None" = None
document_send_stream: MemoryObjectSendStream | None = None
document_receive_stream: MemoryObjectReceiveStream | None = None
shutdown_event: anyio.Event | None = None
scanner_wake_event: anyio.Event | None = None
@property
def eviction_task_group(self) -> TaskGroup | None:
@@ -357,16 +357,14 @@ class OAuthAppContext:
nextcloud_host: str
token_verifier: object # UnifiedTokenVerifier (ADR-005 compliant)
refresh_token_storage: Optional["RefreshTokenStorage"] = None
oauth_client: Optional[object] = None
refresh_token_storage: "RefreshTokenStorage | None" = None
oauth_client: object | None = None
oauth_provider: str = "nextcloud" # "nextcloud" or "keycloak"
server_client_id: Optional[str] = (
None # MCP server's OAuth client ID (static or DCR)
)
document_send_stream: Optional[MemoryObjectSendStream] = None
document_receive_stream: Optional[MemoryObjectReceiveStream] = None
shutdown_event: Optional[anyio.Event] = None
scanner_wake_event: Optional[anyio.Event] = None
server_client_id: str | None = None # MCP server's OAuth client ID (static or DCR)
document_send_stream: MemoryObjectSendStream | None = None
document_receive_stream: MemoryObjectReceiveStream | None = None
shutdown_event: anyio.Event | None = None
scanner_wake_event: anyio.Event | None = None
@property
def eviction_task_group(self) -> TaskGroup | None:
+5 -4
View File
@@ -72,8 +72,9 @@ async def _verify_notes(
doc_id = result.id
# Parse defensively before the network call so a malformed payload
# produces a specific log line, not a generic "unexpected error"
# from the catch-all ``except Exception`` below. The parallel
# implementation in ``_verify_deck_cards`` follows the same pattern.
# from the catch-all ``except Exception`` below. ``_verify_notes``
# is the canonical shape; ``_verify_deck_cards`` and
# ``_verify_news_items`` mirror this hoisted-cast pattern.
try:
note_id_int = int(doc_id)
except (TypeError, ValueError) as e:
@@ -210,8 +211,8 @@ async def _verify_deck_cards(
# Parse defensively before the network call so a malformed payload
# produces a specific log line, not a generic "unexpected error"
# from the catch-all ``except Exception`` below. The parallel
# implementation in ``_verify_news_items`` follows the same pattern.
# from the catch-all ``except Exception`` below. Mirrors the
# canonical hoisted-cast pattern in ``_verify_notes``.
try:
board_id_int = int(board_id)
stack_id_int = int(stack_id)