Skip to content

Commit b7d0fdb

Browse files
committed
docs(extensions): document OperationValidatorExtension.precheck hook from vectorize-io#1548
1 parent 592f01b commit b7d0fdb

1 file changed

Lines changed: 42 additions & 0 deletions

File tree

hindsight-docs/docs/developer/extensions.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,48 @@ class QuotaAwareValidator(OperationValidatorExtension):
246246
`validate_recall` or `validate_reflect` in synchronous HTTP request
247247
paths — there is no queue to defer to and it will surface as a 500.
248248

249+
#### Pre-body-parse precheck
250+
251+
`OperationValidatorExtension` also exposes an optional `precheck` hook that runs
252+
**before the request body is parsed**. FastAPI resolves `Depends` callables
253+
ahead of body deserialisation, so a `precheck` wired onto the billable POST
254+
routes (retain, recall, reflect, files retain, mental-model create + refresh)
255+
can short-circuit a request without ever materialising the JSON payload in
256+
memory. Use it for "should this caller be allowed to spend resources on this
257+
request at all" decisions — an exhausted balance, a revoked key, or a
258+
rate-limited tenant.
259+
260+
The hook receives a `PrecheckContext`, which carries only the cheap,
261+
already-resolved request state (the body is not yet available):
262+
263+
- `operation`: a short route identifier, e.g. `"retain"`, `"recall"`,
264+
`"reflect"`, `"files_retain"`, `"mental_model_create"`,
265+
`"mental_model_refresh"`.
266+
- `bank_id`: parsed from the URL path.
267+
- `request_context`: the authenticated `RequestContext` (tenant already
268+
resolved by the tenant extension).
269+
270+
```python
271+
from hindsight_api.extensions import (
272+
OperationValidatorExtension,
273+
PrecheckContext,
274+
ValidationResult,
275+
)
276+
277+
278+
class QuotaAwareValidator(OperationValidatorExtension):
279+
async def precheck(self, ctx: PrecheckContext) -> ValidationResult:
280+
if await self._balance_exhausted(ctx.bank_id):
281+
return ValidationResult.reject("balance exhausted")
282+
return ValidationResult.accept()
283+
```
284+
285+
Keep `precheck` cheap (prefer cached lookups) and conservative on errors —
286+
prefer `ValidationResult.accept()` so a transient lookup failure does not turn
287+
into a request rejection. The default implementation accepts everything, so the
288+
hook is opt-in; the post-body `validate_*` hooks still run and remain the source
289+
of truth for the precise per-call cost / quota arithmetic.
290+
249291
### Example: Custom MCPExtension
250292

251293
```python

0 commit comments

Comments
 (0)