conceptadev / conceptadev/rockets
Role-based field projection: vary a response's fields by who asked
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 2
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 23
Description
## Problem
A response's shape is fixed at boot: one route, one schema. There is no way
to include a field for one caller and omit it for another on the **same**
route.
The concrete case that motivates this:
> `GET /users/:id`
> - a regular user sees name, email, avatar
> - an admin looking at the same user also needs the internal note, the risk
> score, and why the account was blocked
Same URL, same handler. Only the caller differs.
Returning the extra fields and hoping they pass through does not work, by
design: a response schema strips every key it does not declare. That
fail-closed behavior is what stops accidental leaks and should not be
weakened — it is also exactly what closes this door.
## What exists today
Two routes: `GET /users/:id` and `GET /admin/users/:id`, each with its own
response schema.
That is a legitimate answer, and arguably the clearer one — who sees what is
explicit in the route table instead of hidden behind a label. It costs
duplication, and on a generated CRUD resource, forking a route per role is
not cheap.
## What this is NOT
This is not a regression from the schema engine (#105). `class-transformer`
supports serialization groups, but Rockets never used them: `@Expose({
groups })` and `@SerializeOptions({ groups })` have **zero** occurrences
anywhere in the repository before #105. The always-hide case (`@Exclude()`)
did exist and did migrate — it is the `hidden` field meta today.
So this is a door that was never opened, not one that closed. Filing it so
the gap is a decision on record rather than something discovered when a
consumer asks for it.
## Sketch of a direction
Not a proposal, just the shape the discussion would start from:
- The projection has to run where the response schema already runs, so the
fail-closed guarantee holds for every branch — a projection that widens
the declared shape would defeat the point.
- The selector needs the authenticated actor, which the operation context
already carries.
- Whatever the mechanism, each variant should be documentable: a route whose
response varies by role and says so in only one of its shapes produces an
OpenAPI contract that lies to some of its clients. This is the part most
likely to decide the design.
## Out of scope for now
Not scheduled. Opening it so the tradeoff is visible when the first real
request for it arrives.
Contributor guide
Research direction
Start by reading the response schema behavior introduced in #105 and tracing where it runs for GET /users/:id. Then inspect how the operation context carries the authenticated actor and how response variants would be represented in the OpenAPI contract. Done means a decided, documented design that preserves fail-closed projection for every role.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100