Description
Inside an access policy collection predicate on auth(), the this keyword resolves scalar fields against the @@allow model correctly, but resolves relation fields against the collection's model instead — contradicting the docs and breaking scope-tree RBAC patterns.
Reproduction
Minimal ZModel:
model ActorContext {
id String @id
capabilityGrants DerivedCapabilityGrant[]
@@auth
}
model DerivedCapabilityGrant {
id String @id
actorContextId String
capabilityKey String
grantScopeId String
grantScope AuthScope @relation(fields: [grantScopeId], references: [id])
}
model AuthScope {
id String @id
path String[]
documents DocumentBase[]
}
model DocumentBase {
documentId String @id
tenantId String
authScopeId String
authScope AuthScope @relation(fields: [authScopeId], references: [id])
@@allow('read',
auth() != null &&
auth().tenantId == tenantId &&
auth().capabilityGrants?[
capabilityKey == 'read' &&
(grantScopeId == this.authScopeId || grantScopeId in this.authScope.path)
]
)
}
The ZModel parser accepts grantScopeId in this.authScope.path at generation time. But at runtime, the policy engine crashes:
Error: Field "authScope" not found in model "DerivedCapabilityGrant"
Stack trace:
ExpressionTransformer.transformRelationAccess (expression-transformer.ts:837)
ExpressionTransformer._member (expression-transformer.ts:705)
Expected Behavior
According to the docs, "the keyword this can be used inside the CONDITION to refer to fields belonging to the model where the rule is defined (the outer model)."
So this.authScope.path inside auth().capabilityGrants?[...] should resolve:
this → DocumentBase (the model with @@allow)
this.authScope → DocumentBase.authScope relation
this.authScope.path → AuthScope.path
Actual Behavior
The engine resolves authScope against DerivedCapabilityGrant (the collection's model), which doesn't have that field.
Workaround
check() delegation to AuthScope's @@allow (which doesn't suffer from this issue since this resolves correctly in top-level or check-based predicates)
- Denormalizing the relation field into a scalar array (e.g.,
scopingPath String[] on DocumentBase) and checking grantScopeId in this.scopingPath
Environment
- ZenStack version: 3.8.0
- Provider: PostgreSQL (PGLite but I doubt that caused this bug)
- ZenStack CLI: zen generate
- ORM client
Notes
- Scalar fields on
this work correctly (e.g., grantScopeId == this.authScopeId passes)
- Relation fields on
this behave identically with and without binding variables ([g, ...])
- The failure is at runtime (expression-transformer.ts:837), not at parse time — the ZModel parser accepts the syntax
Description
Inside an access policy collection predicate on
auth(), thethiskeyword resolves scalar fields against the@@allowmodel correctly, but resolves relation fields against the collection's model instead — contradicting the docs and breaking scope-tree RBAC patterns.Reproduction
Minimal ZModel:
The ZModel parser accepts
grantScopeId in this.authScope.pathat generation time. But at runtime, the policy engine crashes:Stack trace:
Expected Behavior
According to the docs, "the keyword
thiscan be used inside the CONDITION to refer to fields belonging to the model where the rule is defined (the outer model)."So
this.authScope.pathinsideauth().capabilityGrants?[...]should resolve:this→DocumentBase(the model with@@allow)this.authScope→DocumentBase.authScoperelationthis.authScope.path→AuthScope.pathActual Behavior
The engine resolves
authScopeagainstDerivedCapabilityGrant(the collection's model), which doesn't have that field.Workaround
check()delegation to AuthScope's@@allow(which doesn't suffer from this issue sincethisresolves correctly in top-level orcheck-based predicates)scopingPath String[]on DocumentBase) and checkinggrantScopeId in this.scopingPathEnvironment
Notes
thiswork correctly (e.g.,grantScopeId == this.authScopeIdpasses)thisbehave identically with and without binding variables ([g, ...])