Documentation
¶
Overview ¶
Package v2breaking detects request shapes whose v1 vs v2 (weighted-graph) resolution behavior is known to differ. It exists so the server can emit telemetry about potential breaking-change exposure before the weighted-graph resolver becomes the default — see the openfga.dev breaking-change writeup.
All predicates here are schema-shape filters only: they read the model, not stored tuples. They may over-report (the request shape matches but no tuple triggers the divergence) but never miss a real divergence.
Index ¶
- Constants
- func CheckExclusionReason(typesys *typesystem.TypeSystem, tk *openfgav1.CheckRequestTupleKey) string
- func CheckReason(typesys *typesystem.TypeSystem, tk *openfgav1.CheckRequestTupleKey) string
- func CheckReasonFromV2Error(err error) string
- func ExpandReason(typesys *typesystem.TypeSystem, targetObjectType, targetRelation string) string
- func ExpandResponseConfirmsReason(reason string, typesys *typesystem.TypeSystem, ...) bool
- func ListUsersReason(typesys *typesystem.TypeSystem, object *openfgav1.Object, relation string, ...) string
- func ListUsersResponseConfirmsReason(reason string, typesys *typesystem.TypeSystem, object *openfgav1.Object, ...) bool
Constants ¶
const ( ReasonSelfReferentialUserset = "self_referential_userset" ReasonAliasUserset = "alias_userset" ReasonComputedUsersetSelfObj = "computed_userset_self_object" ReasonTTUUserset = "ttu_userset" ReasonUsersetWithExclusion = "userset_with_exclusion" ReasonWildcardWithExclusion = "wildcard_with_exclusion" )
Reason constants are stable strings emitted in logs/metrics. Do not rename without coordinating with downstream dashboards.
Variables ¶
This section is empty.
Functions ¶
func CheckExclusionReason ¶
func CheckExclusionReason(typesys *typesystem.TypeSystem, tk *openfgav1.CheckRequestTupleKey) string
CheckExclusionReason returns a non-empty reason string when the Check request's target relation is shaped such that v2 would have rejected the request at request time. Used on the v1 fallback path, where v2Check did not run (or failed before validating request shape) and CheckReasonFromV2Error therefore has no error to map.
Detection:
- Userset user + Difference anywhere in the target rewrite → userset_with_exclusion.
- Non-userset user of type T + Difference whose base branch can reach a T:* wildcard leaf → wildcard_with_exclusion. The reachability walk crosses ComputedUserset and TupleToUserset edges (see walkForWildcardUnderDifference), so shapes where the Difference sits one hop away via TTU are still detected.
func CheckReason ¶
func CheckReason(typesys *typesystem.TypeSystem, tk *openfgav1.CheckRequestTupleKey) string
CheckReason returns a non-empty reason string when the Check request shape matches a known v1→v2 divergence for userset users. The caller is expected to have already verified that the user is a userset (object#relation) and that v2Check returned FALSE — this function does not re-check those.
Shape catalogue:
"self_referential_userset": v1 unconditionally returned TRUE for check(o#r, r, o); v2 evaluates against the schema and returns FALSE.
"alias_userset": the target relation directly accepts T#R' where R' resolves via computed_userset to the user's relation R, and R is not itself directly assignable on the target. v1 follows the alias from a stored tuple, v2 does not.
"computed_userset_self_object": user's object equals the target object, and the user's relation appears as a ComputedUserset leaf in the target relation's rewrite tree.
"ttu_userset": target relation's rewrite contains a TupleToUserset whose computed relation equals the user's relation, AND the user's object type is directly-related to the tupleset relation.
func CheckReasonFromV2Error ¶
CheckReasonFromV2Error maps a v2Check error to a v2-breaking reason when the error is one of the exclusion-shape rejections v2Check emits at request time. Returns "" for any other error (context, throttling, generic failure, etc.).
V2Check rejects two exclusion shapes at request time:
- check.ErrWildcardInvalidRequest — a wildcard user reaches a Difference node (directly or transitively through TTU/ComputedUserset).
- check.ErrUsersetInvalidRequest — a userset user reaches a Difference node.
The v1 fallback path will still produce an answer, so callers should log the reason as a potential-divergence signal alongside the returned v2 error.
func ExpandReason ¶
func ExpandReason(typesys *typesystem.TypeSystem, targetObjectType, targetRelation string) string
ExpandReason returns a non-empty reason string when the Expand request shape matches a known v1→v2 resolution divergence. Compared to Check/ListUsers, Expand has no user input — only (object, relation) — so detection is purely schema-shape against the target relation's rewrite.
ExpandReason covers the shapes whose v1 Expand tree exposes a divergence from v2:
The two exclusion shapes (userset_with_exclusion, wildcard_with_exclusion): v2 rejects the request at request time, so any v1 Expand response on a Difference-containing rewrite is itself the divergence signal.
alias_userset: v1 follows `T#R' → R` aliases when materializing leaves, surfacing aliased usersets that v2's strict storage-validation path would not.
computed_userset_self_object: the rewrite contains a ComputedUserset leaf referring to a sibling relation. v1 Expand emits this leaf as a direct member of the target relation (e.g. `viewer: editor or writer` produces a Union with `document:d1#writer` as a leaf), which is exactly the v1 inference v2's strict resolution would not surface.
ttu_userset: the rewrite contains a TupleToUserset. v1 Expand emits a TTU leaf naming the tupleset relation (e.g. `viewer: viewer from parent` produces a leaf referencing `document:d1#parent`) even when no parent tuples exist in storage — surfacing structural detail that v2's strict resolution would not.
`self_referential_userset` remains a Check-only boolean shortcut — it doesn't change Expand's emitted tree.
Exclusion shapes are checked before the others since they are the more severe divergence (v2 returns a request-time error).
func ExpandResponseConfirmsReason ¶
func ExpandResponseConfirmsReason(reason string, typesys *typesystem.TypeSystem, targetObjectType, targetRelation string, tree *openfgav1.UsersetTree) bool
ExpandResponseConfirmsReason reports whether the Expand response tree is consistent with v1 having actually traversed the divergent path for the given reason. It mirrors ListUsersResponseConfirmsReason: shape predicates fire on schema alone (which may over-report), and this function suppresses the log when the response shows the v1 path wasn't actually exercised.
For the exclusion shapes, v2 would reject the request at request time, so any successful response means v1 actually walked the divergent path. For alias_userset, we confirm that v1 surfaced an aliased directly-related userset (T#R' where R' resolves via ComputedUserset to the queried relation) — that exact userset will not appear in a v2 expansion of the same call.
Returns true for unknown reasons, so the caller's logic stays simple.
func ListUsersReason ¶
func ListUsersReason(typesys *typesystem.TypeSystem, object *openfgav1.Object, relation string, filter *openfgav1.UserTypeFilter) string
ListUsersReason returns a non-empty reason string when the ListUsers request shape matches a known v1→v2 divergence. Compared to CheckReason, it also detects the two exclusion-shape cases that v2Check rejects at request time (rather than silently returning FALSE):
"userset_with_exclusion": the filter is a userset and the target relation's rewrite contains a Difference node.
"wildcard_with_exclusion": the filter is a non-userset of type T and the target relation's rewrite contains a Difference whose base branch can accept a typed wildcard T:*.
func ListUsersResponseConfirmsReason ¶
func ListUsersResponseConfirmsReason(reason string, typesys *typesystem.TypeSystem, object *openfgav1.Object, relation string, filter *openfgav1.UserTypeFilter, users []*openfgav1.User) bool
ListUsersResponseConfirmsReason reports whether the ListUsers response is consistent with v1 having actually traversed the divergent path for the given reason. It is used to suppress false-positive logs on shape-matched requests whose responses didn't observably exercise the v1 behavior.
For the four per-user-shape reasons, this looks for the specific user that v1 surfaces via the divergent path. For the exclusion-shape reasons, v2 would reject the request at request time — so any non-empty v1 response on a Difference-containing relation means v1 actually walked the exclusion path to produce it.
Returns true for unknown reasons, so the caller's logic stays simple.
Types ¶
This section is empty.