conceptadev / conceptadev/rockets

Role-based field projection: vary a response's fields by who asked

Open
#110 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.