github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/typesystem/typesystem.go (about)

     1  package typesystem
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/authzed/spicedb/pkg/datastore"
     8  	"github.com/authzed/spicedb/pkg/genutil/mapz"
     9  	"github.com/authzed/spicedb/pkg/graph"
    10  	nspkg "github.com/authzed/spicedb/pkg/namespace"
    11  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    12  	iv1 "github.com/authzed/spicedb/pkg/proto/impl/v1"
    13  	"github.com/authzed/spicedb/pkg/spiceerrors"
    14  	"github.com/authzed/spicedb/pkg/tuple"
    15  )
    16  
    17  // AllowedDirectRelation indicates whether a relation is allowed on the right side of another relation.
    18  type AllowedDirectRelation int
    19  
    20  const (
    21  	// UnknownIfRelationAllowed indicates that no type information is defined for
    22  	// this relation.
    23  	UnknownIfRelationAllowed AllowedDirectRelation = iota
    24  
    25  	// DirectRelationValid indicates that the specified subject relation is valid as
    26  	// part of a *direct* tuple on the relation.
    27  	DirectRelationValid
    28  
    29  	// DirectRelationNotValid indicates that the specified subject relation is not
    30  	// valid as part of a *direct* tuple on the relation.
    31  	DirectRelationNotValid
    32  )
    33  
    34  // AllowedPublicSubject indicates whether a public subject of a particular kind is allowed on the right side of another relation.
    35  type AllowedPublicSubject int
    36  
    37  const (
    38  	// UnknownIfPublicAllowed indicates that no type information is defined for
    39  	// this relation.
    40  	UnknownIfPublicAllowed AllowedPublicSubject = iota
    41  
    42  	// PublicSubjectAllowed indicates that the specified subject wildcard is valid as
    43  	// part of a *direct* tuple on the relation.
    44  	PublicSubjectAllowed
    45  
    46  	// PublicSubjectNotAllowed indicates that the specified subject wildcard is not
    47  	// valid as part of a *direct* tuple on the relation.
    48  	PublicSubjectNotAllowed
    49  )
    50  
    51  // AllowedRelationOption indicates whether an allowed relation of a particular kind is allowed on the right side of another relation.
    52  type AllowedRelationOption int
    53  
    54  const (
    55  	// UnknownIfAllowed indicates that no type information is defined for
    56  	// this relation.
    57  	UnknownIfAllowed AllowedRelationOption = iota
    58  
    59  	// AllowedRelationValid indicates that the specified subject relation is valid.
    60  	AllowedRelationValid
    61  
    62  	// AllowedRelationNotValid indicates that the specified subject relation is not valid.
    63  	AllowedRelationNotValid
    64  )
    65  
    66  // AllowedNamespaceOption indicates whether an allowed namespace of a particular kind is allowed on the right side of another relation.
    67  type AllowedNamespaceOption int
    68  
    69  const (
    70  	// UnknownIfAllowedNamespace indicates that no type information is defined for
    71  	// this relation.
    72  	UnknownIfAllowedNamespace AllowedNamespaceOption = iota
    73  
    74  	// AllowedNamespaceValid indicates that the specified subject namespace is valid.
    75  	AllowedNamespaceValid
    76  
    77  	// AllowedNamespaceNotValid indicates that the specified subject namespace is not valid.
    78  	AllowedNamespaceNotValid
    79  )
    80  
    81  // NewNamespaceTypeSystem returns a new type system for the given namespace. Note that the type
    82  // system is not validated until Validate is called.
    83  func NewNamespaceTypeSystem(nsDef *core.NamespaceDefinition, resolver Resolver) (*TypeSystem, error) {
    84  	relationMap := make(map[string]*core.Relation, len(nsDef.GetRelation()))
    85  	for _, relation := range nsDef.GetRelation() {
    86  		_, existing := relationMap[relation.Name]
    87  		if existing {
    88  			return nil, NewTypeErrorWithSource(
    89  				NewDuplicateRelationError(nsDef.Name, relation.Name),
    90  				relation,
    91  				relation.Name,
    92  			)
    93  		}
    94  
    95  		relationMap[relation.Name] = relation
    96  	}
    97  
    98  	return &TypeSystem{
    99  		resolver:           resolver,
   100  		nsDef:              nsDef,
   101  		relationMap:        relationMap,
   102  		wildcardCheckCache: nil,
   103  	}, nil
   104  }
   105  
   106  // TypeSystem represents typing information found in a namespace.
   107  type TypeSystem struct {
   108  	resolver           Resolver
   109  	nsDef              *core.NamespaceDefinition
   110  	relationMap        map[string]*core.Relation
   111  	wildcardCheckCache map[string]*WildcardTypeReference
   112  }
   113  
   114  // Namespace is the namespace for which the type system was constructed.
   115  func (nts *TypeSystem) Namespace() *core.NamespaceDefinition {
   116  	return nts.nsDef
   117  }
   118  
   119  // HasTypeInformation returns true if the relation with the given name exists and has type
   120  // information defined.
   121  func (nts *TypeSystem) HasTypeInformation(relationName string) bool {
   122  	rel, ok := nts.relationMap[relationName]
   123  	return ok && rel.GetTypeInformation() != nil
   124  }
   125  
   126  // HasRelation returns true if the namespace has the given relation defined.
   127  func (nts *TypeSystem) HasRelation(relationName string) bool {
   128  	_, ok := nts.relationMap[relationName]
   129  	return ok
   130  }
   131  
   132  // GetRelation returns the relation that's defined with the give name in the type system or returns false.
   133  func (nts *TypeSystem) GetRelation(relationName string) (*core.Relation, bool) {
   134  	rel, ok := nts.relationMap[relationName]
   135  	return rel, ok
   136  }
   137  
   138  // MustGetRelation returns the relation that's defined with the give name in the type system or panics.
   139  func (nts *TypeSystem) MustGetRelation(relationName string) *core.Relation {
   140  	rel, ok := nts.relationMap[relationName]
   141  	if !ok {
   142  		panic("Missing relation")
   143  	}
   144  	return rel
   145  }
   146  
   147  // GetRelationOrError returns the relation with the givne name defined on the namespace, or RelationNotFoundErr if
   148  // not found.
   149  func (nts *TypeSystem) GetRelationOrError(relationName string) (*core.Relation, error) {
   150  	relation, ok := nts.relationMap[relationName]
   151  	if !ok {
   152  		return nil, NewRelationNotFoundErr(nts.nsDef.Name, relationName)
   153  	}
   154  	return relation, nil
   155  }
   156  
   157  // IsPermission returns true if the namespace has the given relation defined and it is
   158  // a permission.
   159  func (nts *TypeSystem) IsPermission(relationName string) bool {
   160  	found, ok := nts.relationMap[relationName]
   161  	if !ok {
   162  		return false
   163  	}
   164  
   165  	return nspkg.GetRelationKind(found) == iv1.RelationMetadata_PERMISSION
   166  }
   167  
   168  // GetAllowedDirectNamespaceSubjectRelations returns the subject relations for the target namespace, if it is defined as appearing
   169  // somewhere on the right side of a relation (except public). Returns nil if there is no type information or it is not allowed.
   170  func (nts *TypeSystem) GetAllowedDirectNamespaceSubjectRelations(sourceRelationName string, targetNamespaceName string) (*mapz.Set[string], error) {
   171  	found, ok := nts.relationMap[sourceRelationName]
   172  	if !ok {
   173  		return nil, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   174  	}
   175  
   176  	typeInfo := found.GetTypeInformation()
   177  	if typeInfo == nil {
   178  		return nil, nil
   179  	}
   180  
   181  	allowedRelations := typeInfo.GetAllowedDirectRelations()
   182  	allowedSubjectRelations := mapz.NewSet[string]()
   183  	for _, allowedRelation := range allowedRelations {
   184  		if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil {
   185  			allowedSubjectRelations.Add(allowedRelation.GetRelation())
   186  		}
   187  	}
   188  
   189  	return allowedSubjectRelations, nil
   190  }
   191  
   192  // IsAllowedDirectNamespace returns whether the target namespace is defined as appearing somewhere on the
   193  // right side of a relation (except public).
   194  func (nts *TypeSystem) IsAllowedDirectNamespace(sourceRelationName string, targetNamespaceName string) (AllowedNamespaceOption, error) {
   195  	found, ok := nts.relationMap[sourceRelationName]
   196  	if !ok {
   197  		return UnknownIfAllowedNamespace, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   198  	}
   199  
   200  	typeInfo := found.GetTypeInformation()
   201  	if typeInfo == nil {
   202  		return UnknownIfAllowedNamespace, nil
   203  	}
   204  
   205  	allowedRelations := typeInfo.GetAllowedDirectRelations()
   206  	for _, allowedRelation := range allowedRelations {
   207  		if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() == nil {
   208  			return AllowedNamespaceValid, nil
   209  		}
   210  	}
   211  
   212  	return AllowedNamespaceNotValid, nil
   213  }
   214  
   215  // IsAllowedPublicNamespace returns whether the target namespace is defined as public on the source relation.
   216  func (nts *TypeSystem) IsAllowedPublicNamespace(sourceRelationName string, targetNamespaceName string) (AllowedPublicSubject, error) {
   217  	found, ok := nts.relationMap[sourceRelationName]
   218  	if !ok {
   219  		return UnknownIfPublicAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   220  	}
   221  
   222  	typeInfo := found.GetTypeInformation()
   223  	if typeInfo == nil {
   224  		return UnknownIfPublicAllowed, nil
   225  	}
   226  
   227  	allowedRelations := typeInfo.GetAllowedDirectRelations()
   228  	for _, allowedRelation := range allowedRelations {
   229  		if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetPublicWildcard() != nil {
   230  			return PublicSubjectAllowed, nil
   231  		}
   232  	}
   233  
   234  	return PublicSubjectNotAllowed, nil
   235  }
   236  
   237  // IsAllowedDirectRelation returns whether the subject relation is allowed to appear on the right
   238  // hand side of a tuple placed in the source relation with the given name.
   239  func (nts *TypeSystem) IsAllowedDirectRelation(sourceRelationName string, targetNamespaceName string, targetRelationName string) (AllowedDirectRelation, error) {
   240  	found, ok := nts.relationMap[sourceRelationName]
   241  	if !ok {
   242  		return UnknownIfRelationAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   243  	}
   244  
   245  	typeInfo := found.GetTypeInformation()
   246  	if typeInfo == nil {
   247  		return UnknownIfRelationAllowed, nil
   248  	}
   249  
   250  	allowedRelations := typeInfo.GetAllowedDirectRelations()
   251  	for _, allowedRelation := range allowedRelations {
   252  		if allowedRelation.GetNamespace() == targetNamespaceName && allowedRelation.GetRelation() == targetRelationName {
   253  			return DirectRelationValid, nil
   254  		}
   255  	}
   256  
   257  	return DirectRelationNotValid, nil
   258  }
   259  
   260  // HasAllowedRelation returns whether the source relation has the given allowed relation.
   261  func (nts *TypeSystem) HasAllowedRelation(sourceRelationName string, toCheck *core.AllowedRelation) (AllowedRelationOption, error) {
   262  	found, ok := nts.relationMap[sourceRelationName]
   263  	if !ok {
   264  		return UnknownIfAllowed, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   265  	}
   266  
   267  	typeInfo := found.GetTypeInformation()
   268  	if typeInfo == nil {
   269  		return UnknownIfAllowed, nil
   270  	}
   271  
   272  	allowedRelations := typeInfo.GetAllowedDirectRelations()
   273  	for _, allowedRelation := range allowedRelations {
   274  		if SourceForAllowedRelation(allowedRelation) == SourceForAllowedRelation(toCheck) {
   275  			return AllowedRelationValid, nil
   276  		}
   277  	}
   278  
   279  	return AllowedRelationNotValid, nil
   280  }
   281  
   282  // AllowedDirectRelationsAndWildcards returns the allowed subject relations for a source relation. Note that this function will return
   283  // wildcards.
   284  func (nts *TypeSystem) AllowedDirectRelationsAndWildcards(sourceRelationName string) ([]*core.AllowedRelation, error) {
   285  	found, ok := nts.relationMap[sourceRelationName]
   286  	if !ok {
   287  		return []*core.AllowedRelation{}, asTypeError(NewRelationNotFoundErr(nts.nsDef.Name, sourceRelationName))
   288  	}
   289  
   290  	typeInfo := found.GetTypeInformation()
   291  	if typeInfo == nil {
   292  		return []*core.AllowedRelation{}, nil
   293  	}
   294  
   295  	return typeInfo.GetAllowedDirectRelations(), nil
   296  }
   297  
   298  // AllowedSubjectRelations returns the allowed subject relations for a source relation. Note that this function will *not*
   299  // return wildcards.
   300  func (nts *TypeSystem) AllowedSubjectRelations(sourceRelationName string) ([]*core.RelationReference, error) {
   301  	allowedDirect, err := nts.AllowedDirectRelationsAndWildcards(sourceRelationName)
   302  	if err != nil {
   303  		return []*core.RelationReference{}, asTypeError(err)
   304  	}
   305  
   306  	filtered := make([]*core.RelationReference, 0, len(allowedDirect))
   307  	for _, allowed := range allowedDirect {
   308  		if allowed.GetPublicWildcard() != nil {
   309  			continue
   310  		}
   311  
   312  		if allowed.GetRelation() == "" {
   313  			return nil, spiceerrors.MustBugf("got an empty relation for a non-wildcard type definition under namespace")
   314  		}
   315  
   316  		filtered = append(filtered, &core.RelationReference{
   317  			Namespace: allowed.GetNamespace(),
   318  			Relation:  allowed.GetRelation(),
   319  		})
   320  	}
   321  	return filtered, nil
   322  }
   323  
   324  // HasIndirectSubjects returns true if and only if there exists at least one non-ellipsis (i.e. indirect) subject
   325  // allowed on the specified relation.
   326  func (nts *TypeSystem) HasIndirectSubjects(sourceRelationName string) (bool, error) {
   327  	allowedRelations, err := nts.AllowedDirectRelationsAndWildcards(sourceRelationName)
   328  	if err != nil {
   329  		return false, asTypeError(err)
   330  	}
   331  
   332  	for _, allowedRelation := range allowedRelations {
   333  		if allowedRelation.GetPublicWildcard() != nil {
   334  			continue
   335  		}
   336  
   337  		if allowedRelation.GetRelation() != tuple.Ellipsis {
   338  			return true, nil
   339  		}
   340  	}
   341  
   342  	return false, nil
   343  }
   344  
   345  // WildcardTypeReference represents a relation that references a wildcard type.
   346  type WildcardTypeReference struct {
   347  	// ReferencingRelation is the relation referencing the wildcard type.
   348  	ReferencingRelation *core.RelationReference
   349  
   350  	// WildcardType is the wildcard type referenced.
   351  	WildcardType *core.AllowedRelation
   352  }
   353  
   354  // referencesWildcardType returns true if the relation references a wildcard type, either directly or via
   355  // another relation.
   356  func (nts *TypeSystem) referencesWildcardType(ctx context.Context, relationName string) (*WildcardTypeReference, error) {
   357  	return nts.referencesWildcardTypeWithEncountered(ctx, relationName, map[string]bool{})
   358  }
   359  
   360  func (nts *TypeSystem) referencesWildcardTypeWithEncountered(ctx context.Context, relationName string, encountered map[string]bool) (*WildcardTypeReference, error) {
   361  	if nts.wildcardCheckCache == nil {
   362  		nts.wildcardCheckCache = make(map[string]*WildcardTypeReference, 1)
   363  	}
   364  
   365  	cached, isCached := nts.wildcardCheckCache[relationName]
   366  	if isCached {
   367  		return cached, nil
   368  	}
   369  
   370  	computed, err := nts.computeReferencesWildcardType(ctx, relationName, encountered)
   371  	if err != nil {
   372  		return nil, err
   373  	}
   374  
   375  	nts.wildcardCheckCache[relationName] = computed
   376  	return computed, nil
   377  }
   378  
   379  func (nts *TypeSystem) computeReferencesWildcardType(ctx context.Context, relationName string, encountered map[string]bool) (*WildcardTypeReference, error) {
   380  	relString := tuple.JoinRelRef(nts.nsDef.Name, relationName)
   381  	if _, ok := encountered[relString]; ok {
   382  		return nil, nil
   383  	}
   384  	encountered[relString] = true
   385  
   386  	allowedRels, err := nts.AllowedDirectRelationsAndWildcards(relationName)
   387  	if err != nil {
   388  		return nil, asTypeError(err)
   389  	}
   390  
   391  	for _, allowedRelation := range allowedRels {
   392  		if allowedRelation.GetPublicWildcard() != nil {
   393  			return &WildcardTypeReference{
   394  				ReferencingRelation: &core.RelationReference{
   395  					Namespace: nts.nsDef.Name,
   396  					Relation:  relationName,
   397  				},
   398  				WildcardType: allowedRelation,
   399  			}, nil
   400  		}
   401  
   402  		if allowedRelation.GetRelation() != tuple.Ellipsis {
   403  			if allowedRelation.GetNamespace() == nts.nsDef.Name {
   404  				found, err := nts.referencesWildcardTypeWithEncountered(ctx, allowedRelation.GetRelation(), encountered)
   405  				if err != nil {
   406  					return nil, asTypeError(err)
   407  				}
   408  
   409  				if found != nil {
   410  					return found, nil
   411  				}
   412  				continue
   413  			}
   414  
   415  			subjectTS, err := nts.TypeSystemForNamespace(ctx, allowedRelation.GetNamespace())
   416  			if err != nil {
   417  				return nil, asTypeError(err)
   418  			}
   419  
   420  			found, err := subjectTS.referencesWildcardTypeWithEncountered(ctx, allowedRelation.GetRelation(), encountered)
   421  			if err != nil {
   422  				return nil, asTypeError(err)
   423  			}
   424  
   425  			if found != nil {
   426  				return found, nil
   427  			}
   428  		}
   429  	}
   430  
   431  	return nil, nil
   432  }
   433  
   434  // Validate runs validation on the type system for the namespace to ensure it is consistent.
   435  func (nts *TypeSystem) Validate(ctx context.Context) (*ValidatedNamespaceTypeSystem, error) {
   436  	for _, relation := range nts.relationMap {
   437  		relation := relation
   438  
   439  		// Validate the usersets's.
   440  		usersetRewrite := relation.GetUsersetRewrite()
   441  		rerr, err := graph.WalkRewrite(usersetRewrite, func(childOneof *core.SetOperation_Child) interface{} {
   442  			switch child := childOneof.ChildType.(type) {
   443  			case *core.SetOperation_Child_ComputedUserset:
   444  				relationName := child.ComputedUserset.GetRelation()
   445  				_, ok := nts.relationMap[relationName]
   446  				if !ok {
   447  					return NewTypeErrorWithSource(
   448  						NewRelationNotFoundErr(nts.nsDef.Name, relationName),
   449  						childOneof,
   450  						relationName,
   451  					)
   452  				}
   453  			case *core.SetOperation_Child_TupleToUserset:
   454  				ttu := child.TupleToUserset
   455  				if ttu == nil {
   456  					return nil
   457  				}
   458  
   459  				tupleset := ttu.GetTupleset()
   460  				if tupleset == nil {
   461  					return nil
   462  				}
   463  
   464  				relationName := tupleset.GetRelation()
   465  				found, ok := nts.relationMap[relationName]
   466  				if !ok {
   467  					return NewTypeErrorWithSource(
   468  						NewRelationNotFoundErr(nts.nsDef.Name, relationName),
   469  						childOneof,
   470  						relationName,
   471  					)
   472  				}
   473  
   474  				if nspkg.GetRelationKind(found) == iv1.RelationMetadata_PERMISSION {
   475  					return NewTypeErrorWithSource(
   476  						NewPermissionUsedOnLeftOfArrowErr(nts.nsDef.Name, relation.Name, relationName),
   477  						childOneof, relationName)
   478  				}
   479  
   480  				// Ensure the tupleset relation doesn't itself import wildcard.
   481  				referencedWildcard, err := nts.referencesWildcardType(ctx, relationName)
   482  				if err != nil {
   483  					return err
   484  				}
   485  
   486  				if referencedWildcard != nil {
   487  					return NewTypeErrorWithSource(
   488  						NewWildcardUsedInArrowErr(
   489  							nts.nsDef.Name,
   490  							relation.Name,
   491  							relationName,
   492  							referencedWildcard.WildcardType.GetNamespace(),
   493  							tuple.StringRR(referencedWildcard.ReferencingRelation),
   494  						),
   495  						childOneof, relationName,
   496  					)
   497  				}
   498  			}
   499  			return nil
   500  		})
   501  		if rerr != nil {
   502  			return nil, asTypeError(rerr.(error))
   503  		}
   504  		if err != nil {
   505  			return nil, err
   506  		}
   507  
   508  		// Validate type information.
   509  		typeInfo := relation.TypeInformation
   510  		if typeInfo == nil {
   511  			continue
   512  		}
   513  
   514  		allowedDirectRelations := typeInfo.GetAllowedDirectRelations()
   515  
   516  		// Check for a _this or the lack of a userset_rewrite. If either is found,
   517  		// then the allowed list must have at least one type.
   518  		hasThis, err := graph.HasThis(usersetRewrite)
   519  		if err != nil {
   520  			return nil, err
   521  		}
   522  
   523  		if usersetRewrite == nil || hasThis {
   524  			if len(allowedDirectRelations) == 0 {
   525  				return nil, NewTypeErrorWithSource(
   526  					NewMissingAllowedRelationsErr(nts.nsDef.Name, relation.Name),
   527  					relation, relation.Name,
   528  				)
   529  			}
   530  		} else {
   531  			if len(allowedDirectRelations) != 0 {
   532  				// NOTE: This is a legacy error and should never really occur with schema.
   533  				return nil, NewTypeErrorWithSource(
   534  					fmt.Errorf("direct relations are not allowed under relation `%s`", relation.Name),
   535  					relation, relation.Name)
   536  			}
   537  		}
   538  
   539  		// Allowed relations verification:
   540  		// 1) that all allowed relations are not this very relation
   541  		// 2) that they exist within the referenced namespace
   542  		// 3) that they are not duplicated in any way
   543  		// 4) that if they have a caveat reference, the caveat is valid
   544  		encountered := mapz.NewSet[string]()
   545  
   546  		for _, allowedRelation := range allowedDirectRelations {
   547  			source := SourceForAllowedRelation(allowedRelation)
   548  			if !encountered.Add(source) {
   549  				return nil, NewTypeErrorWithSource(
   550  					NewDuplicateAllowedRelationErr(nts.nsDef.Name, relation.Name, source),
   551  					allowedRelation,
   552  					source,
   553  				)
   554  			}
   555  
   556  			// Check the namespace.
   557  			if allowedRelation.GetNamespace() == nts.nsDef.Name {
   558  				if allowedRelation.GetPublicWildcard() == nil && allowedRelation.GetRelation() != tuple.Ellipsis {
   559  					_, ok := nts.relationMap[allowedRelation.GetRelation()]
   560  					if !ok {
   561  						return nil, NewTypeErrorWithSource(
   562  							NewRelationNotFoundErr(allowedRelation.GetNamespace(), allowedRelation.GetRelation()),
   563  							allowedRelation,
   564  							allowedRelation.GetRelation(),
   565  						)
   566  					}
   567  				}
   568  			} else {
   569  				subjectTS, err := nts.TypeSystemForNamespace(ctx, allowedRelation.GetNamespace())
   570  				if err != nil {
   571  					return nil, NewTypeErrorWithSource(
   572  						fmt.Errorf("could not lookup definition `%s` for relation `%s`: %w", allowedRelation.GetNamespace(), relation.Name, err),
   573  						allowedRelation,
   574  						allowedRelation.GetNamespace(),
   575  					)
   576  				}
   577  
   578  				// Check for relations.
   579  				if allowedRelation.GetPublicWildcard() == nil && allowedRelation.GetRelation() != tuple.Ellipsis {
   580  					// Ensure the relation exists.
   581  					ok := subjectTS.HasRelation(allowedRelation.GetRelation())
   582  					if !ok {
   583  						return nil, NewTypeErrorWithSource(
   584  							NewRelationNotFoundErr(allowedRelation.GetNamespace(), allowedRelation.GetRelation()),
   585  							allowedRelation,
   586  							allowedRelation.GetRelation(),
   587  						)
   588  					}
   589  
   590  					// Ensure the relation doesn't itself import wildcard.
   591  					referencedWildcard, err := subjectTS.referencesWildcardType(ctx, allowedRelation.GetRelation())
   592  					if err != nil {
   593  						return nil, err
   594  					}
   595  
   596  					if referencedWildcard != nil {
   597  						return nil, NewTypeErrorWithSource(
   598  							NewTransitiveWildcardErr(
   599  								nts.nsDef.Name,
   600  								relation.GetName(),
   601  								allowedRelation.Namespace,
   602  								allowedRelation.GetRelation(),
   603  								referencedWildcard.WildcardType.GetNamespace(),
   604  								tuple.StringRR(referencedWildcard.ReferencingRelation),
   605  							),
   606  							allowedRelation,
   607  							tuple.JoinRelRef(allowedRelation.GetNamespace(), allowedRelation.GetRelation()),
   608  						)
   609  					}
   610  				}
   611  			}
   612  
   613  			// Check the caveat, if any.
   614  			if allowedRelation.GetRequiredCaveat() != nil {
   615  				_, err := nts.resolver.LookupCaveat(ctx, allowedRelation.GetRequiredCaveat().CaveatName)
   616  				if err != nil {
   617  					return nil, NewTypeErrorWithSource(
   618  						fmt.Errorf("could not lookup caveat `%s` for relation `%s`: %w", allowedRelation.GetRequiredCaveat().CaveatName, relation.Name, err),
   619  						allowedRelation,
   620  						source,
   621  					)
   622  				}
   623  			}
   624  		}
   625  	}
   626  
   627  	return &ValidatedNamespaceTypeSystem{nts}, nil
   628  }
   629  
   630  // SourceForAllowedRelation returns the source code representation of an allowed relation.
   631  func SourceForAllowedRelation(allowedRelation *core.AllowedRelation) string {
   632  	caveatStr := ""
   633  
   634  	if allowedRelation.GetRequiredCaveat() != nil {
   635  		caveatStr = " with " + allowedRelation.RequiredCaveat.CaveatName
   636  	}
   637  
   638  	if allowedRelation.GetPublicWildcard() != nil {
   639  		return tuple.JoinObjectRef(allowedRelation.Namespace, "*") + caveatStr
   640  	}
   641  
   642  	if rel := allowedRelation.GetRelation(); rel != tuple.Ellipsis {
   643  		return tuple.JoinRelRef(allowedRelation.Namespace, rel) + caveatStr
   644  	}
   645  
   646  	return allowedRelation.Namespace + caveatStr
   647  }
   648  
   649  // TypeSystemForNamespace returns a type system for the given namespace.
   650  func (nts *TypeSystem) TypeSystemForNamespace(ctx context.Context, namespaceName string) (*TypeSystem, error) {
   651  	if nts.nsDef.Name == namespaceName {
   652  		return nts, nil
   653  	}
   654  
   655  	nsDef, err := nts.resolver.LookupNamespace(ctx, namespaceName)
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  
   660  	return NewNamespaceTypeSystem(nsDef, nts.resolver)
   661  }
   662  
   663  // RelationDoesNotAllowCaveatsForSubject returns true if and only if it can be conclusively determined that
   664  // the given subject type does not accept any caveats on the given relation. If the relation does not have type information,
   665  // returns an error.
   666  func (nts *TypeSystem) RelationDoesNotAllowCaveatsForSubject(relationName string, subjectTypeName string) (bool, error) {
   667  	relation, ok := nts.relationMap[relationName]
   668  	if !ok {
   669  		return false, NewRelationNotFoundErr(nts.nsDef.Name, relationName)
   670  	}
   671  
   672  	typeInfo := relation.GetTypeInformation()
   673  	if typeInfo == nil {
   674  		return false, NewTypeErrorWithSource(
   675  			fmt.Errorf("relation `%s` does not have type information", relationName),
   676  			relation, relationName,
   677  		)
   678  	}
   679  
   680  	foundSubjectType := false
   681  	for _, allowedRelation := range typeInfo.GetAllowedDirectRelations() {
   682  		if allowedRelation.GetNamespace() == subjectTypeName {
   683  			foundSubjectType = true
   684  			if allowedRelation.GetRequiredCaveat() != nil && allowedRelation.GetRequiredCaveat().CaveatName != "" {
   685  				return false, nil
   686  			}
   687  		}
   688  	}
   689  
   690  	if !foundSubjectType {
   691  		return false, NewTypeErrorWithSource(
   692  			fmt.Errorf("relation `%s` does not allow subject type `%s`", relationName, subjectTypeName),
   693  			relation, relationName,
   694  		)
   695  	}
   696  
   697  	return true, nil
   698  }
   699  
   700  // ValidatedNamespaceTypeSystem is validated type system for a namespace.
   701  type ValidatedNamespaceTypeSystem struct {
   702  	*TypeSystem
   703  }
   704  
   705  // NewTypeErrorWithSource creates a new type error at the specific position and with source code, wrapping the underlying
   706  // error.
   707  func NewTypeErrorWithSource(wrapped error, withSource nspkg.WithSourcePosition, sourceCodeString string) error {
   708  	sourcePosition := withSource.GetSourcePosition()
   709  	if sourcePosition != nil {
   710  		return asTypeError(spiceerrors.NewErrorWithSource(
   711  			wrapped,
   712  			sourceCodeString,
   713  			sourcePosition.ZeroIndexedLineNumber+1, // +1 to make 1-indexed
   714  			sourcePosition.ZeroIndexedColumnPosition+1, // +1 to make 1-indexed
   715  		))
   716  	}
   717  
   718  	return asTypeError(spiceerrors.NewErrorWithSource(
   719  		wrapped,
   720  		sourceCodeString,
   721  		0,
   722  		0,
   723  	))
   724  }
   725  
   726  // ReadNamespaceAndTypes reads a namespace definition, version, and type system and returns it if found.
   727  func ReadNamespaceAndTypes(
   728  	ctx context.Context,
   729  	nsName string,
   730  	ds datastore.Reader,
   731  ) (*core.NamespaceDefinition, *ValidatedNamespaceTypeSystem, error) {
   732  	nsDef, _, err := ds.ReadNamespaceByName(ctx, nsName)
   733  	if err != nil {
   734  		return nil, nil, err
   735  	}
   736  
   737  	ts, terr := NewNamespaceTypeSystem(nsDef, ResolverForDatastoreReader(ds))
   738  	if terr != nil {
   739  		return nil, nil, terr
   740  	}
   741  
   742  	// NOTE: since the type system was read from the datastore, it must have been validated
   743  	// on the way in, so it is safe for us to return it as a validated type system.
   744  	return nsDef, &ValidatedNamespaceTypeSystem{ts}, nil
   745  }