feat(agent-bff): expose relation list and count with foreign-collection validation#1746
Conversation
3 new issues
|
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (3)
🛟 Help
|
…on validation Serve POST /v1/:collection/relations/:relation/list and /count. The parent drives only route resolution (path + parentId); projection/filter/sort/count run against the foreign collection, so the BFF owns the agent query shape and the parent-vs-foreign projection quirk never surfaces. - to-many relations only; to-one/polymorphic/absent -> unknown_relation 404 - parentId is opaque (int/uuid/composite), forwarded unchanged, url-encoded - missing/empty/malformed parentId -> invalid_request 400, no agent call - reuse flat-mapping/packed-id/count-deactivation helpers; guard not wired Fixes PRD-672 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- drop encodeURIComponent in relationPath: HttpRequester.buildUrl already runs the path through escapeUrlSlug/encodeURI, so pre-encoding double- encoded composite/spaced parentIds (`a|b` -> `a%257Cb`) - guard the foreign collection against the allow-list in handleRelation: the agent's count-related authorizes only the parent, so an unguarded foreign collection leaked a hidden collection's rows/count - add a client test exercising the raw relation path and a disallowed foreign-collection test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d10c98b to
a65f89e
Compare
Review — important findingsMulti-perspective review (correctness, security, tests, types). No Critical bugs; the error pipeline and the parent/foreign authorization design are solid. The items below are worth resolving before merge. 🔴 Nested-relation field-path guard is not wired on relation routes → foreign-of-foreign authorization gap
The top-level handlers call The underlying authority gap is real: the agent's Fix: call 🟡
|
…y relation body parsing - wire assertNoRelationFieldPaths in the relation list/count handlers: the agent's list-related asserts browse only on the immediate foreign collection, so a nested `:`-path traversed to a third collection whose browse is never checked (same class as the top-level guard). Plain foreign fields are unaffected. - fold parentId into parseRelationListRequest/parseRelationCountRequest so the relation body types carry a required parentId instead of a dead optional field read via a cast. - tests: BelongsToMany list with a differently-named foreign PK proving __forest uses the foreign key; count-side 404s (unknown/to-one/disallowed foreign); nested-path 422 on both list and count; relation parser units. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review addressed in 4e43287Thanks — all three resolved. 🔴 Nested-relation guard on relation routes — fixedYou're right, and it corrects my own reasoning: the guard rejects only
🟡 Dead relation-body types — fixed
🟡 Test gaps — fixed
589 tests green, lint + build clean. |
…the 404 The foreign-collection allow-list rejection echoed the internally-derived collection name into the response body, letting a caller enumerate hidden collections by probing a parent relation. Drop the identifier from the message (the parent case keeps it since the caller supplied that name). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

What
Exposes the relation read surface on the BFF:
POST /v1/:collection/relations/:relation/listand/count. The relation route resolves authorization/allow-list on the parent, but projection, filter, sort, and count run against the foreign collection — the BFF owns the correct agent query shape so the agent's non-uniform relation precedent (parent-vs-foreign projection serialization) never leaks.Builds on PRD-671's shared helpers (flat mapping, packed-id, count-deactivation).
How
data-routes-middlewarewith a second regex\/agent\/v1\/:collection\/relations\/:relation\/(list|count), reusing the existing token/read-model/error plumbing.HasMany/BelongsToMany). To-one, polymorphic (multi-target), and absent relations map tounknown_relation(404). Verified against both the Node agent (per-type route generation) and the Ruby agent (onlyPolymorphicManyToOnecarries multiple targets, and it is to-one).parentId+ relation name go only into the agent path;fields[FOREIGN], filters, sort, and count run on the foreign collection. A test proves the parent is used solely for route resolution.invalid_request(400) with no agent call.mapListResponse/mapCountResponse(foreign-side) and PRD-670 error mapping. The nested-relation guard is deliberately NOT wired (relation routes legitimately accept foreign fields).No agent or agent-client package changes; all changes are agent-bff-local.
Tests
data[]+__forestforeign +countStatus: not_requested)fields[posts], notfields[users])parentId(agent not called)parentIdpassthrough unchangedparseParentIdunit casesyarn workspace @forestadmin/agent-bff lint test buildall green (541 tests).Fixes PRD-672
🤖 Generated with Claude Code
Note
Add relation list and count endpoints to agent-bff data routes middleware
POST /agent/v1/:collection/relations/:relation/listand.../countendpoints to data-routes-middleware.ts, routing through a newhandleRelationorchestrator that validates relation existence, allow-list status, and foreign collection before dispatching.listRelation()andcountRelationRaw()to theAgentDataClientinterface and factory in agent-data-client.ts; count requests bypass deserialization viaskipDeserialization: true.parseRelationListRequestandparseRelationCountRequestin agent-query.ts with aparseParentIdvalidator that accepts strings and finite numbers, rejecting empty/whitespace/non-finite values with a 400invalid_request.HasMany,BelongsToMany) are listable; to-one and polymorphic relations return 404. Nested relation field paths are rejected with 400.unknown_collectionrather than a data error.Macroscope summarized 3ab6908.