Skip to content

feat(agent-bff): serve the static OpenAPI document behind the agent gate - #1810

Open
Tonours wants to merge 1 commit into
chore/dedupe-zod-single-v4-resolutionfrom
feature/prd-886-openapi-static-document
Open

feat(agent-bff): serve the static OpenAPI document behind the agent gate#1810
Tonours wants to merge 1 commit into
chore/dedupe-zod-single-v4-resolutionfrom
feature/prd-886-openapi-static-document

Conversation

@Tonours

@Tonours Tonours commented Aug 6, 2026

Copy link
Copy Markdown
Member

Stacked on #1809 (chore/dedupe-zod-single-v4-resolution). The base is that branch, not main: #1809 must be reviewed and merged first, and the diff below is only this branch's commit. #1809 is a hard prerequisite, not a convenience: @asteasolutions/zod-to-openapi takes zod as a peer dependency, so without the dedupe yarn can bind it to a different zod copy than the one agent-bff builds its schemas from.

Serves GET /agent/openapi.json: an OpenAPI 3.1 document describing the six generic BFF routes, generated from Zod, built once at boot.

fixes PRD-886

The mount point is the auth gate

The route sits at /agent/openapi.json, inside the agentScoped chain, right after the auth middlewares. That is deliberate. The BFF has no deny-by-default gate: auth applies only to paths under /agent, and /health is the one hardcoded public exact-match ahead of the chain. Mounting at the root would have made the document public and required a bespoke gate; mounting under /agent inherits the existing one, so an unauthenticated request gets a 401 before path matching even happens.

Placed before the timezone middleware and before buildAgentRouteMiddlewares, so it still answers when AGENT_URL is unset and the data routes fall back to the stub. It never calls the agent.

Written against the code, not the spec

The PRD-648 spike is dead (its base predates the whole package), so this is from scratch on main. Its patterns survive: extendZodWithOpenApi load-once, registry plus OpenApiGeneratorV31, a serializeOpenApi shared with the future CLI. Its content did not: six of its claims were wrong against today's code:

Contract Reality Source
Path prefix /agent/v1, not /v1 data-routes-middleware.ts:41
Filter recursive condition tree, not flat capabilities-validator.ts:19-38
Operators PascalCase, required operator-normalizer.ts:10-28
sort[].direction optional, ascending by default agent-query.ts:126
page.offset must be a multiple of limit agent-query.ts:135
parentId string or number agent-query.ts:176-188
recordIds required, even when empty action-routes-middleware.ts:42

The document declares every status each route can actually return, including the ones a proxy hits most: 502 on transport failure, 413 on the 16kb body cap, 429 on agent rate-limiting. 501 is declared only on execute, the one route that produces it, and points at a separate schema because that body carries no message field.

Scope and safety

Additive. No existing route, handler, or schema changed. The only edit outside src/openapi/ is one line in the cli-core.ts middleware chain.

The document is static: it describes generic routes with path placeholders and holds no tenant data. The only environment-derived value is the package version, served to authenticated callers only. Per-collection unfolding, which does expose real names, is PRD-684.

How to test

yarn workspace @forestadmin/agent-bff test
yarn workspace @forestadmin/agent-bff lint

Against a running agent:

FOREST_AUTH_SECRET=... FOREST_ENV_SECRET=... FOREST_SERVER_URL=... \
FOREST_APP_URL=... AGENT_URL=http://localhost:3009/forest HTTP_PORT=4599 \
node packages/agent-bff/dist/cli.js

curl -i localhost:4599/agent/openapi.json                        # 401, no document
curl -i localhost:4599/openapi.json                              # 404
curl -s -H "Authorization: Bearer <session>" \
  localhost:4599/agent/openapi.json | npx @redocly/cli lint -    # valid

Ran on this branch: 767 tests pass (726 before, 41 added), lint and build clean. redocly lint now runs inside the suite rather than by hand, so the document cannot go invalid unnoticed.

An end-to-end pass against the live playground agent on :3009 covered the gate (401 with no credentials, no leak in the body, unauthorized type), the root path (404), the authenticated fetch (200), redocly validation of the document as served over HTTP, ten contract assertions, and the documented error envelope. 9 checks, all passing. Eight path variants (trailing slash, case change, %6f, dot segments, .., doubled slash) were probed unauthenticated: none serves the document.

Known limitations

Action 200 responses are typed unknown. mapActionExecuteResult has a concrete shape and describing it belongs with the action work, not here.

Relation list and count skip capabilities validation entirely, so on those two routes the 422 field errors are unreachable and the filter depth cap is not enforced. The ConditionTree description says so rather than pretending otherwise. #1801 is the branch fixing that.

The document is a static description of generic routes. A consumer cannot yet generate a client that knows real collection names or per-field operators. That is PRD-684 and PRD-685.

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

@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

PRD-886

Comment thread packages/agent-bff/src/openapi/openapi-document.ts Outdated
Comment thread packages/agent-bff/src/openapi/openapi-document.ts
Comment thread packages/agent-bff/src/openapi/schemas.ts
@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from ba29239 to dc83756 Compare August 6, 2026 17:19
Comment thread packages/agent-bff/src/openapi/schemas.ts
@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch 3 times, most recently from 595c4b6 to ece634c Compare August 6, 2026 17:48
@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from ece634c to b11c332 Compare August 6, 2026 20:48
@qltysh

qltysh Bot commented Aug 7, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (5)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent-bff/src/cli-core.ts100.0%
New Coverage rating: A
packages/agent-bff/src/openapi/openapi-document.ts100.0%
New Coverage rating: A
packages/agent-bff/src/openapi/zod-openapi.ts100.0%
New Coverage rating: A
packages/agent-bff/src/openapi/schemas.ts100.0%
New Coverage rating: A
packages/agent-bff/src/openapi/openapi-routes.ts91.7%20
Total98.6%
馃 Increase coverage with AI coding...
In the `feature/prd-886-openapi-static-document` branch, add test coverage for this new code:

- `packages/agent-bff/src/openapi/openapi-routes.ts` -- Line 20

馃殾 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.

@Tonours
Tonours force-pushed the chore/dedupe-zod-single-v4-resolution branch from 3e81c4b to 04d3ba9 Compare August 7, 2026 07:39
@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from d51081e to 9652148 Compare August 7, 2026 07:42
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