Enforcing Request Policy at the Runtime Boundary
In a multi-tenant business system, the dangerous query is often not obviously dangerous.
A developer writes a useful request:
Q.candidates().selectName()
.selectEmail()
.filterBySkill("Java")
.page(1, 20)
.comment("Query candidates").purpose("Load data")
.executeForList(ctx);
The request looks typed, generated, and harmless. But in a platform like Multi Talent, a candidate belongs to a customer account, a workspace, a recruiter team, and often a legal region. A useful query becomes unsafe if it can see outside those boundaries.
That is why TeaQL treats request execution as a runtime boundary, not just a method call.
The Multi Talent Problem
Imagine Multi Talent as a SaaS platform for recruiting agencies and enterprise hiring teams.
The same TeaQL model may include:
- candidates;
- talent profiles;
- resumes and attachments;
- interview records;
- offer workflows;
- customer accounts;
- recruiter teams;
- regional compliance metadata.
The query language should stay expressive. A team should be able to search candidates by skill, location, availability, interview status, and related job opening.
But every query must also respect infrastructure and customer boundaries:
- customer A must never read customer B's candidates;
- a recruiter can only see candidates assigned to their team or allowed pool;
- regional data residency policy may limit which records can be loaded;
- raw SQL escape hatches should not bypass tenant rules;
- unlimited list requests should not become infrastructure abuse.
Those rules should not be copied into every controller.
The Wrong Place for the Rule
One option is to add filters wherever a query is written:
Q.candidates().filterByCustomer(ctx.currentCustomer())
.filterByRecruiterTeam(ctx.currentTeam())
.filterBySkill("Java")
.comment("Query candidates").purpose("Load data")
.executeForList(ctx);
This works until one path forgets the filter. It also asks every feature author to understand every infrastructure and data-protection rule.
Another option is to hide the query behind service methods. That can work for some workflows, but TeaQL deliberately gives teams a generated request language. The runtime should make that language safe instead of forcing teams to abandon it.
The Runtime Boundary
TeaQL Java runtime exposes a dedicated UserContext extension point for this
final step:
protected <T extends Entity> SearchRequest<T> enforceRequestPolicy(SearchRequest<T> request) {
return request;
}
Every normal request execution path goes through this hook before the request is submitted to the repository:
SearchRequest
-> UserContext
-> enforceRequestPolicy(...)
-> Repository
-> database provider
That placement matters. It is late enough to see the actual request that is about to execute, but still early enough to change it or reject it.
TeaQL Rust has the same runtime-boundary idea through RequestPolicy on
UserContext. The Rust hook is platform-scoped and runs after entity-scoped
repository behavior, so it can make the final decision before the request
reaches the provider.
