Skip to content

feat(agent-bff): validate filters, sorts, and projections from capabilities#1755

Open
nbouliol wants to merge 2 commits into
feature/prd-674-expose-the-action-execute-endpoint-with-result-normalizationfrom
feature/prd-675-validate-filters-fields-sorts-and-projections-from
Open

feat(agent-bff): validate filters, sorts, and projections from capabilities#1755
nbouliol wants to merge 2 commits into
feature/prd-674-expose-the-action-execute-endpoint-with-result-normalizationfrom
feature/prd-675-validate-filters-fields-sorts-and-projections-from

Conversation

@nbouliol

@nbouliol nbouliol commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

Adds a reusable, pure validator in agent-bff that checks a list/count request's filter, sort, and projection fields against the target collection's capabilities before any agent call, plus an operator normalizer. Capabilities are the only source of truth — no hardcoded type table is consulted, which fixes the drift between the frontend operator tables and what the agent actually supports.

This is the reusable validator only. Wiring it into the endpoints is out of scope (PRD-676/677).

How

  • operator-normalizer.ts — maps agent snake_case capabilities operators to the canonical PascalCase set. The snake→Pascal map is derived from datasource-toolkit allOperators using the same transform the agent uses to serialize them, so it round-trips every operator and cannot drift.
  • validation-errors.ts — three error factories owned by this slice, reusing the existing error-envelope shape ({ error: { type, status, message, details } }) without touching the shared registry.
  • capabilities-validator.tsvalidateAgainstCapabilities() returns every offending field as a BffHttpError[] (empty = valid); assertValidAgainstCapabilities() throws the first. Walks every filter leaf (including under And/Or groups).

Validation rules

Surface Rule Error
filter field absent from capabilities 422 unknown_field
filter field present with no operators 422 field_not_filterable
filter operator not in the field's normalized operators 400 invalid_filter_operator (+ validOperators)
sort field absent 422 unknown_field
projection field absent 422 unknown_field

Sort/projection check existence only; operator checks apply to filters. Each error carries the offending details.field.

Notable decisions

  • Normalize against the full allOperators set, not just uniqueOperators as the ticket wording suggests — real capabilities include in, contains, blank, etc.
  • A relation field appears in capabilities with no operators key (not operators: []); treated as field_not_filterable via operators ?? []. CapabilitiesResult.operators made optional to match runtime. See the ticket comment for the invariant discrepancy.
  • An unmapped capabilities operator throws 500 mapping_error (loud) rather than dropping or passing it through — this only happens on an agent/BFF version skew.

Tests

22 new focused unit tests: normalizer round-trip over all operators + edge cases; every error case; a fully-valid request; a mapping_error case; and an assertion that a field absent from capabilities is rejected with no type table consulted. Full suite: 660/660 green.

Fixes PRD-675

🤖 Generated with Claude Code

Note

Validate filters, sorts, and projections against collection capabilities in agent-bff

  • Adds validateAgainstCapabilities in capabilities-validator.ts to check request filter fields/operators, sort fields, and projection fields against collection capabilities, returning structured BffHttpError arrays.
  • Adds operator-normalizer.ts to convert between PascalCase and snake_case operator formats, bridging the agent's serialization format and the canonical @forestadmin/datasource-toolkit operator names.
  • Adds three typed error factories (unknownField, fieldNotFilterable, invalidFilterOperator) in validation-errors.ts with standardized HTTP status codes (400/422).
  • Risk: throws a 500 mapping_error if a capabilities operator cannot be normalized to a canonical name.

Macroscope summarized dc02e94.

…lities

Add a reusable, pure validator that checks a request's filter, sort, and
projection fields against a collection's capabilities before any agent call,
plus an operator normalizer mapping agent snake_case operators to the canonical
PascalCase set from datasource-toolkit `allOperators`.

- unknown_field (422) for a filter/sort/projection field absent from capabilities
- field_not_filterable (422) for a filter field present with no operators
- invalid_filter_operator (400) with validOperators for an unsupported operator
- sort/projection check existence only; capabilities is the only source of truth
- unmapped capabilities operator throws 500 mapping_error (version skew)

`CapabilitiesResult.operators` made optional to match runtime (relations omit it).

Fixes PRD-675

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 13, 2026

Copy link
Copy Markdown

PRD-675

Comment thread packages/agent-bff/src/validation/capabilities-validator.ts Outdated
A condition-tree leaf always carries an operator; one without a valid
operator is malformed and must not reach the agent. Previously such a leaf
bypassed operator validation and was forwarded untouched.

Co-Authored-By: Claude Opus 4.8 (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.

1 participant