Skip to content

Gate every relationship path on exposeRelationships - #11

Merged
evoactivity merged 2 commits into
mainfrom
fix/expose-relationships-gates-relationship-endpoints
Aug 7, 2026
Merged

Gate every relationship path on exposeRelationships#11
evoactivity merged 2 commits into
mainfrom
fix/expose-relationships-gates-relationship-endpoints

Conversation

@evoactivity

Copy link
Copy Markdown
Owner

Closes #10.

The problem

isRelationExposed is documented as the single home for the relation-visibility rule, shared by serialization and ?include= validation. Two write paths never went through it:

  • The relationship endpoints resolve relations through getRelationOrFail, which honoured serializeAs: null and ignored exposeRelationships. Registering a relationships controller quietly reopened a hidden relation, for reads and writes, on all five routes.
  • The deserializer had its own inline serializeAs check, so a POST or PATCH body with a relationships member naming a hidden relation was accepted.

The fix

getRelationOrFail takes the registry and applies isRelationExposed. The parameter is required rather than optional, so a new call site cannot skip the check, which is how the drift happened in the first place. The function is internal, so the signature change is not a public API change. The deserializer consults the same rule for relationship members, replacing its inline check.

The relation name is resolved before the exposure check, so a kebab URL segment is checked under its Lucid name rather than its URL form.

Hidden means indistinguishable from nonexistent

On every path, a hidden relation behaves exactly like one that was never defined. The endpoints return 404, where the name is a URL segment. Write bodies get the unknown-member 400. A 403 would confirm the relation exists, which is the thing being hidden.

Behaviour change

Anyone using exposeRelationships together with a relationships controller loses routes that were reachable by mistake. Worth a minor bump rather than a patch.

Tests

16 new tests in tests/unit/relationship_endpoint_exposure.spec.ts, written red first: all five relationship routes reject a hidden relation, hidden and unknown are indistinguishable on both status and title, and the guards, exposed relations still serve, resources without exposeRelationships keep every relation, unregistered models are unaffected. The to-many write tests use a manyToMany relation deliberately, since hasMany rejects replace and remove with 403 regardless and would mask the gate.

Unit suite 128 passed, example app functional suite 52 passed, lint and typecheck clean.

Also adds a .prettierignore for pnpm-lock.yaml and CHANGELOG.md in a separate commit, so pnpm format stops burying real changes in reformatting noise.

isRelationExposed was documented as the single home for the
relation-visibility rule, but two write paths never went through it.
The relationship endpoints used getRelationOrFail, which honoured
serializeAs and ignored exposeRelationships, and the deserializer had
its own inline serializeAs check, so both a relationships controller
and a resource POST or PATCH body could reach a hidden relation.

getRelationOrFail now takes the registry and applies the rule. The
parameter is required rather than optional so a new call site cannot
skip the check, which is how the drift happened. The deserializer
consults the same rule for relationship members.

A hidden relation is indistinguishable from one that does not exist
on every path: 404 on the endpoints, where the name is a URL segment,
and the unknown-member 400 in write bodies. A 403 would confirm the
relation exists.

Closes #10
pnpm owns the lockfile and release-plan owns the changelog. Formatting
either buries real changes in noise.
@evoactivity evoactivity added bug Something isn't working enhancement New feature or request and removed bug Something isn't working labels Aug 6, 2026
@evoactivity
evoactivity merged commit 4a67c79 into main Aug 7, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

exposeRelationships is ignored by the relationship endpoints

1 participant