feat(#179 Option C): pydantic body models for POST /search/* endpoints - #181
Merged
Conversation
Closes the second half of #179. After PR #180 moved query-param read endpoints to FastAPI dependencies, the three POST body endpoints (/search/hybrid, /search/keyword, /search/age-fused) were still using inline body.get(...) + validate/normalize calls. Add three pydantic models in search_models.py: - SearchKeywordBody (4 fields) - SearchHybridBody (8 fields incl. fusion_mode/candidate_strategy) - SearchAgeFusedBody (7 fields incl. graph_top_k/fusion_k) Each model has field validators that route: - wing → rooms.normalize_wing_filter - room → rooms.validate_room_or_raise - query → strip+nonempty - fusion_mode → enum check (hybrid only) Refactor each endpoint signature from `async def X(request: Request, x_api_key=...)` → `async def X(body: TheModel, x_api_key=...)` Handler bodies now read body.query / body.wing / etc. — no inline parsing or validation needed. Pydantic ValidationError maps to HTTP 422 (was previously inline HTTP 400 for explicit checks). Test updates: - test_search_hybrid_fusion_mode: construct SearchHybridBody from dict; expected status for invalid fusion_mode 400 → 422 - test_search_age_fused_hydration: construct SearchAgeFusedBody from dict All 534 tests pass. Together with PR #180, this closes the structural enforcement gap from #179: every wing/room-accepting endpoint now canonicalizes at the request-parse layer, not at the handler-body call-site. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
jphein
added a commit
that referenced
this pull request
May 29, 2026
…e) (#182) After #180 + #181 migrated all 6 read endpoints to dependency/pydantic canonicalization, /backfill-age is the simplest write surface — 4 body fields, admin-only, low-traffic. Apply the same pattern as a template for the remaining 4 write surfaces. Adds BackfillAgeBody to search_models.py with field_validator routing wing through rooms.normalize_wing_filter. All fields optional with safe defaults so the endpoint still accepts an empty POST body (curl -X POST .../backfill-age with no Content-Type). Refactor signature from `async def backfill_age(request, x_api_key)` → `async def backfill_age(request, body: BackfillAgeBody = Body(default_factory=...), x_api_key)` Handler body reads body.wing / body.skip_palace / etc. instead of body.get("wing") / .get("skip_palace"). The `request` parameter is preserved because the lifespan-cleanup code reads request.app.state.active_mines (subprocess tracking from #138/#139). Tests pass (534). No behavior change beyond moving the wing canonicalization from handler-body to parse-time. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jphein
added a commit
that referenced
this pull request
May 29, 2026
Updates HANDOFF.md to reflect the full 39-PR session: - Adds the wing/room canonicalization sweep (#172-#178) - Adds the #179 architectural follow-up (#180/#181/#182) - Updates counts: 39 PRs / 534 tests / 30+ deploys - Notes that #179 is partially complete: 1/5 write surfaces migrated to pydantic (BackfillAgeBody via #182); 4 remain with documented per-endpoint empty-wing semantics + suggested order Also adds a section observing the autonomous-loop discovery pattern that worked this session — "fix the obvious bug, write a curl probe, sweep for what else has this shape." Documents that the Stop hook's literal "ongoing imperative" framing is satisfied when marginal value per cycle drops below context-thrash cost. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jphein
added a commit
that referenced
this pull request
May 29, 2026
…183) Continues the write-side #179 migration. /silent-save accepts a Stop- hook diary checkpoint write — same model pattern as BackfillAgeBody in #182 but with write-side wing semantics: empty wing stays as "" rather than coercing to "unknown", so the existing daemon warning ("wing is empty — diary entry will have no wing association") still fires for empty inputs. Adds SilentSaveBody to search_models.py: - entry: required, stripped, non-empty (raises ValueError → 422) - wing: optional, normalized if set, empty preserved - topic, agent_name, themes, message_count, session_id: pass-through Refactor signature from `silent_save(request, x_api_key)` → `silent_save(body: SilentSaveBody, x_api_key)` Handler body uses body.entry / body.wing / etc. and converts to dict via body.model_dump() for the existing helpers (_enqueue_pending_write, _do_silent_save_write) which take the raw payload for rebuild-queue serialization. One test update: test_missing_entry_still_rejected_with_400 → test_missing_entry_still_rejected with 400→422 expectation. Contract of "entry is required" preserved; HTTP code surface shifted to pydantic's standard for missing-required-field (matches #181's SearchHybridBody fusion_mode change). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jphein
added a commit
that referenced
this pull request
May 29, 2026
#188) Fifth and final write surface for #179. The watcher isn't an HTTP endpoint so it can't take a pydantic body — but the same architectural goal applies: canonicalize wing at the *input* boundary so all downstream code can trust the WatchTarget is canonical. Pre-#179: parse_watch_dirs ran ``normalize_wing_name`` only on path-derived wings (when env entry was bare ``path`` with no ``=wing``). Explicit ``path=Wing_Name`` env entries were passed through verbatim, and a defensive ``_normalize_wing_slug`` ran later in _internal_mine before subprocess spawn. Post-#179: parse_watch_dirs normalizes both branches (path-derived AND explicit) through normalize_wing_name. WatchTargets always carry canonical slugs. The use-time normalize in _internal_mine drops to a comment explaining the new invariant. Test added: ``Palace_Daemon`` and ``palace_daemon`` env entries produce identical WatchTarget.wing values ("palace_daemon"). Was the shape of the pre-#175/#178 bug class — mixed-case writes paired with canonical reads producing empty result sets. This closes the wing-canonicalization migration that began with #172. All 11 wing/room-accepting sites in palace-daemon now canonicalize at the input boundary: Read side (#180): GET /search, GET /list, GET /search/fast — FastAPI Depends() Write body (#181, #182, #183, #184, #186, #187): POST /search/keyword, POST /search/hybrid, POST /search/age-fused, POST /backfill-age, POST /silent-save, POST /mine, POST /memory — pydantic body models in search_models.py Internal env (this PR): PALACE_WATCH_DIRS via watcher.parse_watch_dirs Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the second half of #179. After PR #180 moved query-param endpoints to FastAPI dependencies, three POST body endpoints still used inline
body.get(...)+ validate/normalize calls.Adds 3 pydantic models in
search_models.pywith field validators that route wing/room through the shared canonicalization helpers. Refactors/search/hybrid,/search/keyword,/search/age-fusedto accept the models via FastAPI body params.Together with #180, every wing/room-accepting endpoint in the daemon now canonicalizes at the request-parse layer rather than at the handler-body call-site — the structural enforcement #179 wanted.