Documentation
¶
Overview ¶
Package profile enforces the bounded subset of OpenFGA that Zitadel can compile into portable relational query plans (ADR 032 §2).
Supported: direct relations, computed usersets, union, userset references, and tuple-to-userset bounded by known hierarchy edges. Everything else is rejected at upload time with structured Diagnostics rather than failing or timing out during a check.
Contextual tuples are not validated here because they are a check-time API input, not part of an authorization model; the resolver rejects them on request hot paths.
Index ¶
Constants ¶
const SupportedSchemaVersion = "1.1"
SupportedSchemaVersion is the only OpenFGA schema version the profile accepts. Modular models (1.2) are not planned for the MVP.
Variables ¶
var DefaultHierarchyTypes = []string{"app", "app_group", "project", "team", "user"}
DefaultHierarchyTypes are the object types a tuple-to-userset may traverse. Bounding traversal to known edges keeps a check's join depth predictable on both PostgreSQL and Spanner.
Functions ¶
Types ¶
type Code ¶
type Code string
Code identifies a profile violation. Codes are stable so the schema upload API can render them inside details and clients can branch on them. They stay plain snake_case; the dotted authz.* prefix belongs on the outer [domain.Error] that wraps Diagnostics at the service boundary. Where OpenFGA already names the same condition (openfga/api ErrorCode), the code reuses that vocabulary.
const ( // CodeUnsupportedSchemaVersion mirrors OpenFGA 2057: the model declares a // schema version we do not accept. CodeUnsupportedSchemaVersion Code = "unsupported_schema_version" // CodeTypeNotFound mirrors OpenFGA 2021: a restriction names a type the // model never defines. CodeTypeNotFound Code = "type_not_found" // CodeRelationNotFound mirrors OpenFGA 2022: a rewrite or restriction names // a relation the target type never defines. CodeRelationNotFound Code = "relation_not_found" )
Codes reused from OpenFGA's own ErrorCode enum (openfga/api, openfga/v1/errors_ignore.proto). These name conditions OpenFGA itself rejects, so a customer who knows OpenFGA meets the same word here.
const ( // CodeUnsupportedOperator: an `and` or `but not` expression. The profile // starts with union only. CodeUnsupportedOperator Code = "unsupported_operator" // CodeUnsupportedRewrite: a relation whose definition the IR could not // classify — a parser or profile gap rather than an authoring mistake. CodeUnsupportedRewrite Code = "unsupported_rewrite" // CodeUnsupportedWildcard: a `[type:*]` restriction, which has no bounded // expansion in a list predicate. CodeUnsupportedWildcard Code = "unsupported_wildcard" // CodeUnsupportedCondition: a caveat definition or a `with <condition>` // restriction, which would need attribute evaluation off the indexed path. CodeUnsupportedCondition Code = "unsupported_condition" // CodeUnboundedRecursion: a relation that resolves back to itself, so its // closure cannot be precomputed at upload time. CodeUnboundedRecursion Code = "unbounded_recursion" // CodeUnboundedTupleToUserset: a `<relation> from <tupleset>` whose tupleset // is not a plain direct assignment onto a known hierarchy edge. CodeUnboundedTupleToUserset Code = "unbounded_tuple_to_userset" // CodeMissingTypeRestriction: a direct assignment with no `[...]` list. // Distinct from OpenFGA's empty_relation_definition (2023), which is about a // relation carrying no definition at all. CodeMissingTypeRestriction Code = "missing_type_restriction" // CodeEmptyRelationDefinition: a union (or nested union) with no assignable // leaves — present in the catalog but never satisfiable. Crafted JSON can // produce this; the DSL grammar cannot. CodeEmptyRelationDefinition Code = "empty_relation_definition" )
Codes specific to Zitadel's profile. OpenFGA accepts every construct below; we reject it because it cannot be compiled into a bounded, portable query plan (ADR 032 §2). OpenFGA's nearest concept, authorization_model_resolution_too_complex (2002), is a runtime complexity signal rather than an upload-time rejection, so it is deliberately not reused.
type Diagnostic ¶
Diagnostic is a single profile violation. Type, Relation, and Condition are set when the violation can be attributed to one of them.
func (Diagnostic) Error ¶
func (d Diagnostic) Error() string
type Diagnostics ¶
type Diagnostics []Diagnostic
Diagnostics is the full set of violations found in one model. Validation collects every violation instead of failing on the first, so an upload reports all problems at once.
func (Diagnostics) Error ¶
func (d Diagnostics) Error() string
type Validator ¶
type Validator struct {
// HierarchyTypes bounds tuple-to-userset traversal.
// - nil: use [DefaultHierarchyTypes]
// - non-nil empty: allow no TTU edges (deny all hierarchy traversal)
// - non-empty: allow only the listed types
HierarchyTypes []string
}
Validator checks a model against the supported profile.