Skip to content

feat(agent-bff): validate top-level list and count against capabilities#1759

Open
nbouliol wants to merge 1 commit into
feature/prd-675-validate-filters-fields-sorts-and-projections-fromfrom
feature/prd-676-wire-filter-validation-into-top-level-list-and-count-with
Open

feat(agent-bff): validate top-level list and count against capabilities#1759
nbouliol wants to merge 1 commit into
feature/prd-675-validate-filters-fields-sorts-and-projections-fromfrom
feature/prd-676-wire-filter-validation-into-top-level-list-and-count-with

Conversation

@nbouliol

@nbouliol nbouliol commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

Wires the capabilities validator into the top-level list and count handlers in agent-bff, so a request's filter/sort/projection is validated against the agent's real capabilities before the agent call — catching unsupported operators early with a structured error instead of discovering them late.

Changes

  • data-routes-middleware.ts
    • handleList validates filter + sort + projection; handleCount validates the filter only (the count contract carries no sort/projection).
    • Validation runs after the syntactic nested-relation guard and before the agent call.
    • Request-handler deps now carry store / agentUrl / token; a shared resolveCapabilities helper builds the token-bound fetcher and reads the capabilities cache.
  • read-model-store.ts — sharpened the deferred-TOCTOU TODO to name both halves of the race (the middleware read-model snapshot + the in-flight capabilities fetch). Comment-only, no behavior change.
  • Tests — list/count operator + unknown-field cases, read-order assertions (capabilities read before the agent, both paths), valid-request pass-through, and a Zendesk stripeEmail/Equal regression using a raw snake_case fixture so the validator's snake→Pascal normalization path is exercised (returns 400 invalid_filter_operator with validOperators = the field's supported operators, no agent call).

Notes

  • Stacked on PRD-675 (the validator itself). This PR targets the PRD-675 branch; it should merge after PRD-675.
  • TOCTOU coupling of the read-model + capabilities generations is intentionally deferred (documented in the TODO) — the trigger is a 24h-TTL schema refresh landing exactly during a request, and the agent stays the final validator.
  • Relation endpoint wiring is out of scope (PRD-677).

Verification

  • tsc --noEmit clean; changed files lint clean.
  • Full @forestadmin/agent-bff suite: 671/671 passing.

Fixes PRD-676

🤖 Generated with Claude Code

Note

Validate list and count requests against agent capabilities before forwarding in agent-bff

  • Adds a resolveCapabilities helper in data-routes-middleware.ts that fetches capabilities for the current collection via the read-model store and an agent-backed fetcher.
  • handleList now validates filter operators, sort fields, and projection fields against capabilities before invoking the agent; invalid requests are rejected locally.
  • handleCount validates the filter against capabilities before invoking the agent.
  • RequestHandlerDeps is extended with store, agentUrl, and token to support capability lookups.
  • Behavioral Change: list and count requests with unsupported operators or unknown fields are now rejected at the BFF layer rather than forwarded to the agent.
📊 Macroscope summarized b34a423. 2 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Wire the capabilities validator into the top-level list and count handlers
so filter/sort/projection are checked before the agent call. List passes
filter+sort+projection; count passes only the filter. Adds a Zendesk
regression pinning that an unsupported operator on stripeEmail is rejected
early with 400 invalid_filter_operator and the field's normalized operators,
with no agent call.

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-676

Comment on lines +69 to +74
function resolveCapabilities(deps: RequestHandlerDeps): Promise<CapabilitiesResult> {
return deps.store.getCapabilities(
deps.collection,
createAgentCapabilitiesFetcher({ agentUrl: deps.agentUrl, token: deps.token }),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium data/data-routes-middleware.ts:69

resolveCapabilities calls deps.store.getCapabilities directly, so when the capability fetch hits a transport failure or agent 5xx on a cache miss, the raw error reaches the generic error middleware as 500 internal_error instead of the 502 network_error / 503 agent_unavailable mapping that callAgent applies everywhere else. Wrap the fetch in callAgent so capability-endpoint failures preserve the agent error contract.

Suggested change
function resolveCapabilities(deps: RequestHandlerDeps): Promise<CapabilitiesResult> {
return deps.store.getCapabilities(
deps.collection,
createAgentCapabilitiesFetcher({ agentUrl: deps.agentUrl, token: deps.token }),
);
}
+function resolveCapabilities(deps: RequestHandlerDeps): Promise<CapabilitiesResult> {
+ return callAgent(
+ () => deps.store.getCapabilities(
+ deps.collection,
+ createAgentCapabilitiesFetcher({ agentUrl: deps.agentUrl, token: deps.token }),
+ ),
+ deps.logger,
+ );
+}
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/agent-bff/src/data/data-routes-middleware.ts around lines 69-74:

`resolveCapabilities` calls `deps.store.getCapabilities` directly, so when the capability fetch hits a transport failure or agent 5xx on a cache miss, the raw error reaches the generic error middleware as `500 internal_error` instead of the `502 network_error` / `503 agent_unavailable` mapping that `callAgent` applies everywhere else. Wrap the fetch in `callAgent` so capability-endpoint failures preserve the agent error contract.

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