Skip to content

feat(extensions): add OperationValidator.precheck pre-body-parse hook#1548

Merged
nicoloboschi merged 1 commit into
mainfrom
fix/credit-check-before-body-parse
May 25, 2026
Merged

feat(extensions): add OperationValidator.precheck pre-body-parse hook#1548
nicoloboschi merged 1 commit into
mainfrom
fix/credit-check-before-body-parse

Conversation

@cdbartholomew

Copy link
Copy Markdown
Contributor

Summary

  • Adds an optional precheck() method to OperationValidator that lets an extension gate a request before its body is read.
  • Wired as a FastAPI Depends ahead of the body parameter on the billable POST routes (retain, recall, reflect, files retain, mental-model create + refresh).
  • Default is a no-op accept; existing validators are unaffected. Useful when an extension wants to short-circuit a request that would otherwise allocate a large body just to be rejected post-parse (e.g., quota exhausted).

Test plan

  • +7 unit tests in tests/test_extensions.py covering: default no-op accept, rejection short-circuits before body parse (asserted via Pydantic model_validator(mode='before') recorder — body never deserialized on reject), rejection returns the validator's status code + reason, GET routes are unaffected, accept lets request through to body parse, body-parse-skipped behaviour holds for retain/recall/reflect.
  • All 7 new tests + 8 pre-existing non-DB tests pass locally (uv run pytest tests/test_extensions.py → 15 passed). The 21 errors visible in the same run are pre-existing environment issues (missing LLM API key, alembic state) on main itself — verified independent of this PR.

Add an optional ``precheck`` method to ``OperationValidatorExtension`` that
extensions can override to gate a request *before* its body is read off the
wire. Wire it as a FastAPI ``Depends`` ahead of the body parameter on the
billable POST routes (retain, recall, reflect, file retain, mental-model
create, mental-model refresh) so a rejecting precheck short-circuits the
request without ever materialising the JSON payload in memory.

The post-body-parse ``validate_retain`` / ``validate_recall`` /
``validate_reflect`` hooks are unchanged and remain the source of truth for
precise per-call cost and quota arithmetic. ``precheck`` is intentionally a
cheap, side-effect-free check — its sole purpose is to let an extension
short-circuit work that would otherwise allocate the request body
unnecessarily (e.g. a quota-exhausted caller submitting many large bodies).

Why before body parse:

FastAPI resolves dependencies before deserialising the route's body
parameter. A validator that runs only after parse — i.e. inside the route
handler's body — sees the already-materialised request, which is the wrong
layer for "this caller should not be allowed to spend resources on this
request at all" decisions. Wiring as ``Depends`` puts the gate at the right
layer with a one-line change per route.

Verified:

- FastAPI 0.125.0 resolves ``Depends`` raising ``HTTPException`` before
  Pydantic deserialises the body, regardless of declaration order. A
  reproducer using a ``model_validator(mode='before')`` recorder confirms
  zero body-parse calls on the rejection path.
- The new ``PrecheckContext`` carries only operation name + bank_id +
  request_context (already-resolved tenant). No body access — by design.
- Default ``precheck`` returns ``ValidationResult.accept()``; existing
  validators are unaffected.

Tests: +7 unit tests covering the default no-op, the FastAPI Depends
wiring, accept/reject paths, status-code/reason propagation, and explicit
"body never parsed on rejection" assertions for retain / recall / reflect
plus a "GET routes are unaffected" guard. All passing.
@cdbartholomew cdbartholomew requested a review from nicoloboschi May 8, 2026 18:04
@nicoloboschi nicoloboschi merged commit db7dabc into main May 25, 2026
66 of 70 checks passed
r266-tech added a commit to r266-tech/hindsight that referenced this pull request May 25, 2026
r0gig0r added a commit to r0gig0r/hindsight that referenced this pull request May 26, 2026
Notable upstream additions pulled in:
- feat(api): clear endpoint for mental model content (vectorize-io#1706)
- feat(api): per-operation LLM concurrency caps (vectorize-io#1738)
- feat(typescript-client): concrete generated types (replace Promise<any>)
- feat(reranker): Alibaba Qwen3-Rerank support (vectorize-io#1501)
- feat: opencode-go LLM provider (vectorize-io#1652)
- feat(extensions): OperationValidator.precheck pre-body-parse hook (vectorize-io#1548)
- feat(right-agent): new Right Agent integration (vectorize-io#1599)
- fix(ollama): ollama-cloud provider + native API auth (vectorize-io#1734)
- fix(reflect): hide disabled tools from agent system prompt (vectorize-io#1740)
- fix(retain): split oversized single items in batch retain (vectorize-io#1736)
- fix: escape literal braces in user-supplied prompt fields (vectorize-io#1728)
- fix(mental-models): full refresh pending delta baselines (vectorize-io#1684)
- fix(api): lazy load reflect tiktoken encoding (vectorize-io#1654)
- fix(api): reject blank retain content (vectorize-io#1685)
- fix(api): auto-refresh openai-codex OAuth access_token (vectorize-io#1637)
- fix(api): gzip middleware for graph payloads (vectorize-io#1731)
- fix(reranker): detect pre-normalized scores; rank-based fallback (vectorize-io#1512)

Conflicts: only package-lock.json files (took upstream, npm install verified)

Fork customizations verified intact (all 14 checks):
- duplicate_checker_fn streaming Phase 1.5 in orchestrator
- FallbackLLMProvider + CircuitBreaker (fallback_llm.py)
- Single-fact consolidation mode (is_fallback_active routing)
- recallExp + Jaccard dedup + compact memory formatter (plugin)
- Codex 5.1-codex-mini reasoning guard
- Infinity reranker /models fallback in cross_encoder.py
- diversity.py + deduplication.py fork-only modules retained

Tests:
- openclaw vitest: 267/267 pass
- ruff: clean
- tsc --noEmit: clean
- pytest: pre-existing env-config flakes (need HINDSIGHT_API_LLM_API_KEY);
  upstream commit 90cb145 acknowledged as pre-existing CI flakes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants