Skip to content

feat(agent-bff): synthesize capabilities for legacy lianas - #1893

Merged
Tonours merged 7 commits into
fix/agent-bff-derive-primary-keyfrom
feat/v1-capabilities-fallback
Sep 11, 2026
Merged

Tonours merged 7 commits into
fix/agent-bff-derive-primary-keyfrom
feat/v1-capabilities-fallback

Conversation

@Tonours

@Tonours Tonours commented Sep 10, 2026

Copy link
Copy Markdown
Member

Top of a three-PR stack: #1894 then #1895 merge first, and this one carries the last three commits. Review those three; the parents' diffs belong to their own PRs.

What

The BFF serves constrained reads — filter, sort, projection — in front of a v1 liana (forest-express-sequelize, forest-express-mongoose, forest-rails). It decides from the liana the schema announces. BFF_LEGACY_CAPABILITIES is gone.

Why

v1 lianas never served POST /forest/_internal/capabilities. Every constrained read died on that 404 and the collection was unusable. The first shape of this feature asked the operator to declare the generation with a flag, for information the BFF already receives in the schema's meta.liana.

How

forestadmin-client stops dropping the schema's meta, so the liana name reaches the BFF. On a 404 from the capabilities route, the fetcher matches meta.liana against the three legacy names and synthesizes from the apimap: the operator set that liana implements per column type, plus the fields the schema marks filterable and sortable. Any other liana keeps throwing.

includes_all is not published. Measured on a scalar column it answers 500 with a leaking SQL fragment in Express and 422 in Rails, so advertising it would advertise a crash.

The third commit is a refactor the synthesis needs: three implementations converted a PascalCase condition tree to the snake_case the wire expects, each with its own idea of what to do with an operand-less leaf. They agreed on all 43 operators when measured, which is luck, not a contract. The walk now lives in one exported place.

Scope and safety

No agent code changes, v1 or v2. Nothing in the wire format is new to either generation: v2 already parses snake_case operators and accepts value: null on operand-less leaves.

SchemaFetcher now returns { collections, meta } instead of a bare array. The in-process dispatcher added by #1876 is the one other consumer and its test follows the new shape.

How to test

yarn workspace @forestadmin/agent-bff test — 1822 tests, 95 suites. Also measured on five running agents: forest-express-sequelize, forest-express-mongoose, forest-rails, a v2 node agent and a v2 ruby agent, with the capabilities route absent on the three v1 ones. Constrained reads answer 200 on all five. A cold OpenAPI unfold is pinned on a 269-collection schema, the largest measured on the fleet.

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

@qltysh

qltysh Bot commented Sep 10, 2026

Copy link
Copy Markdown

3 new issues

Tool Category Rule Count
qlty Structure Function with high complexity (count = 14): createAgentCapabilitiesFetcher 2
qlty Structure Function with many returns (count = 4): synthesizeCapabilities 1

@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch from b7fce47 to d607cbc Compare September 10, 2026 16:43
@Tonours
Tonours changed the base branch from main to fix/agent-bff-derive-primary-key September 10, 2026 16:44
@qltysh

qltysh Bot commented Sep 10, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (16)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-bff/src/read-model/forest-schema-client.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/openapi/unfolded-paths.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/read-model/schema-cache.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/read-model/agent-capabilities-fetcher.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/openapi/collect-unfolding.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/validation/operator-normalizer.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/data/agent-query.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/validation/validation-errors.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/read-model/read-model-store.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-bff/src/validation/capabilities-validator.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-client/src/query-serializer.ts100.0%
Coverage rating: A Coverage rating: A
packages/forestadmin-client/src/schema/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/agent-client/src/index.ts100.0%
Coverage rating: A Coverage rating: A
packages/forestadmin-client/src/permissions/forest-http-api.ts100.0%
New file Coverage rating: A
packages/agent-client/src/filter-wire-format.ts100.0%
New file Coverage rating: A
packages/agent-bff/src/read-model/synthesize-capabilities.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Comment thread packages/agent-client/src/filter-wire-format.ts
@Tonours
Tonours force-pushed the fix/agent-bff-derive-primary-key branch from 3be7bf2 to 990d725 Compare September 10, 2026 17:04
@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch from d607cbc to eb3f9cf Compare September 10, 2026 17:04
Comment thread packages/agent-bff/src/read-model/synthesize-capabilities.ts
Comment thread packages/agent-bff/src/read-model/schema-cache.ts
@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch 2 times, most recently from 403641b to 817e759 Compare September 10, 2026 17:43
Comment thread packages/agent-bff/src/read-model/synthesize-capabilities.ts Outdated
@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch from 817e759 to 8760afb Compare September 10, 2026 18:37
@Tonours
Tonours force-pushed the fix/agent-bff-derive-primary-key branch from 990d725 to 64e7bf7 Compare September 10, 2026 18:37
Comment thread packages/forestadmin-client/src/schema/index.ts Outdated
@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch 2 times, most recently from fb639f5 to 7fb2459 Compare September 11, 2026 08:34

@nbouliol nbouliol left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Spec: no Linear ticket is linked on this PR, so the functional check was limited to the PR description. The change matches it.

Applies to: the PR as a whole
Claude Opus 5 (claude-opus-5): Should fix: no ADR records that the BFF decides an agent's capabilities from the liana name it self-reports.

Code

agent-capabilities-fetcher.ts:67 reads meta.liana from a cached schema and treats three self-reported names as a capability set.

A way out: run /adr and record the decision against the capability negotiation it replaced.

Reproduction
  1. Read docs/adr in agent-nodejs.
  2. It holds one ADR, about case-insensitive decisions.
  3. Search the org ADR corpus for capabilities, schema, liana, operators and filter.
  4. None covers this decision.
  5. Read the diff, which adds no ADR file.

Comment thread packages/agent-bff/src/openapi/unfolded-document.ts
Comment thread packages/agent-bff/src/read-model/synthesize-capabilities.ts Outdated
Comment thread packages/agent-client/src/filter-wire-format.ts
@Tonours
Tonours force-pushed the feat/v1-capabilities-fallback branch from 7fb2459 to c34380e Compare September 11, 2026 09:47
@nbouliol

Copy link
Copy Markdown
Member

Reopened: no ADR records deciding an agent's capabilities from the liana name it self-reports.

How it was checked

Predicate: an accepted ADR covering the decision to read meta.liana as a capability set.

docs/adr at 1d9c219 holds one file, about case-insensitive decisions, and the head adds none.

No reply addressed the finding, so the rationale still lives only in file comments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXZTKo9YNJS6HNQevQ4LUJ
@nbouliol

Copy link
Copy Markdown
Member

Closed: an accepted ADR now records deciding a v1 agent's capabilities from the liana name the schema carries.

How it was checked

Predicate: an accepted ADR covering the decision to read meta.liana as a capability set.

The ADR added in 2ac05bf is status: accepted and names the self-reported name as an accepted trade-off.

Nothing pins a decision record, so the ADR file itself is where the absence is accounted for.

@nbouliol nbouliol left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Four findings raised, four closed: the sort enum no longer advertises a sort the runtime refuses, an array column publishes no operator, a non-enumerated aggregator is a 400 before any agent call, and the liana-name decision is recorded in an accepted ADR. Three carry a regression test that fails on the pre-fix code.

Two nits left deliberately unaddressed, neither worth holding this up: schemas.ts:521 still describes details as { field } on unknown_field and field_not_filterable only, though field_not_sortable carries one too, and SchemaCache.meta has no production caller since getPayload() replaced it.

LLM Integration Tests (ai-proxy) is red but failed identically before the fixes and this PR touches no ai-proxy file. Merge order still stands: #1894, then #1895.

@Tonours
Tonours merged commit 53cdab3 into fix/agent-bff-derive-primary-key Sep 11, 2026
32 of 33 checks passed
@Tonours
Tonours deleted the feat/v1-capabilities-fallback branch September 11, 2026 13:00
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