Skip to content

Bug: this.relationField resolves against collection model inside auth().collection?[...] predicates #2733

Description

@hechang27-sprt

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:

  1. thisDocumentBase (the model with @@allow)
  2. this.authScopeDocumentBase.authScope relation
  3. this.authScope.pathAuthScope.path

Actual Behavior

The engine resolves authScope against DerivedCapabilityGrant (the collection's model), which doesn't have that field.

Workaround

  1. check() delegation to AuthScope's @@allow (which doesn't suffer from this issue since this resolves correctly in top-level or check-based predicates)
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions