Repository: apigee/registry
Pull request #529 (2022-04-14 18:10:29)

Support for uploading scoreDefinition and ScoreCardDefinition
Summary of my changes:
Each of the following changes are in a separate commit.

* Refactor: Move common utilities from controller to new package "patterns" (This refactor will be useful for future implementation, it is not immediately useful in this PR)
This refactor is required because there are a lot of controller utilities which can be reused for scoring. Example: reading patterns like `$resource.spec/artifacts/lint-spectral`. Hence, those common functions are moved to a separate package `patterns`. Since we planning to eventually get rid of the `core` package, these utils were not put there intentionally.
* Support for `ScoreDefinition` and `ScoreCardDefinition` upload: 
   -  TODO: yaml uploads
   - TODO: validation functions for definitions 
* Support for `ScoreDefinition` and `ScoreCardDefinition` print/get
* Support for `Score` and `ScoreCard` upload: 
   -  TODO: yaml uploads
* Support for `Score` and `ScoreCard` print/get

codecov[bot]: # [Codecov](https://codecov.io/gh/apigee/registry/pull/529?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee) Report
> :exclamation: No coverage uploaded for pull request base (`main@66168c9`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee#section-missing-base-commit).
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main     #529   +/-   ##
=======================================
  Coverage        ?   40.39%           
=======================================
  Files           ?       68           
  Lines           ?     8023           
  Branches        ?        0           
=======================================
  Hits            ?     3241           
  Misses          ?     4381           
  Partials        ?      401           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/apigee/registry/pull/529?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/apigee/registry/pull/529?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee). Last update [66168c9...7543b09](https://codecov.io/gh/apigee/registry/pull/529?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apigee).

shrutiparabgoogle: > Just a couple of minor formatting/spelling issues - from another conversation, I think these protos might not be final, but I'm ok with making that update in a followup.

Yes, the proto update will be in a follow-up PR

Patch:
diff --git a/cmd/registry/cmd/upload/artifact.go b/cmd/registry/cmd/upload/artifact.go
index 574acc0..5e1cc59 100644
--- a/cmd/registry/cmd/upload/artifact.go
+++ b/cmd/registry/cmd/upload/artifact.go
@@ -90,6 +90,14 @@ func buildArtifact(ctx context.Context, parent string, filename string) (*rpc.Ar
 		artifact, err = buildManifestArtifact(ctx, parent, jsonBytes)
 	case "ReferenceList", patch.ReferenceListMimeType:
 		artifact, err = buildReferenceListArtifact(ctx, jsonBytes)
+	case "Score", patch.ScoreMimeType:
+		artifact, err = buildScoreArtifact(ctx, jsonBytes)
+	case "ScoreCard", patch.ScoreCardMimeType:
+		artifact, err = buildScoreCardArtifact(ctx, jsonBytes)
+	case "ScoreCardDefinition", patch.ScoreCardDefinitionMimeType:
+		artifact, err = buildScoreCardDefinitionArtifact(ctx, jsonBytes)
+	case "ScoreDefinition", patch.ScoreDefinitionMimeType:
+		artifact, err = buildScoreDefinitionArtifact(ctx, jsonBytes)
 	case "TaxonomyList", patch.TaxonomyListMimeType:
 		artifact, err = buildTaxonomyListArtifact(ctx, jsonBytes)
 	default:
@@ -185,3 +193,63 @@ func buildTaxonomyListArtifact(ctx context.Context, jsonBytes []byte) (*rpc.Arti
 		MimeType: patch.TaxonomyListMimeType,
 	}, nil
 }
+
+func buildScoreDefinitionArtifact(ctx context.Context, jsonBytes []byte) (*rpc.Artifact, error) {
+	m := &rpc.ScoreDefinition{}
+	if err := protojson.Unmarshal(jsonBytes, m); err != nil {
+		return nil, err
+	}
+	artifactBytes, err := proto.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return &rpc.Artifact{
+		Contents: artifactBytes,
+		MimeType: patch.ScoreDefinitionMimeType,
+	}, nil
+}
+
+func buildScoreCardDefinitionArtifact(ctx context.Context, jsonBytes []byte) (*rpc.Artifact, error) {
+	m := &rpc.ScoreCardDefinition{}
+	if err := protojson.Unmarshal(jsonBytes, m); err != nil {
+		return nil, err
+	}
+	artifactBytes, err := proto.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return &rpc.Artifact{
+		Contents: artifactBytes,
+		MimeType: patch.ScoreCardDefinitionMimeType,
+	}, nil
+}
+
+func buildScoreArtifact(ctx context.Context, jsonBytes []byte) (*rpc.Artifact, error) {
+	m := &rpc.Score{}
+	if err := protojson.Unmarshal(jsonBytes, m); err != nil {
+		return nil, err
+	}
+	artifactBytes, err := proto.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return &rpc.Artifact{
+		Contents: artifactBytes,
+		MimeType: patch.ScoreMimeType,
+	}, nil
+}
+
+func buildScoreCardArtifact(ctx context.Context, jsonBytes []byte) (*rpc.Artifact, error) {
+	m := &rpc.ScoreCard{}
+	if err := protojson.Unmarshal(jsonBytes, m); err != nil {
+		return nil, err
+	}
+	artifactBytes, err := proto.Marshal(m)
+	if err != nil {
+		return nil, err
+	}
+	return &rpc.Artifact{
+		Contents: artifactBytes,
+		MimeType: patch.ScoreCardMimeType,
+	}, nil
+}
diff --git a/cmd/registry/controller/controller.go b/cmd/registry/controller/controller.go
index d41768a..4bc7dec 100644
--- a/cmd/registry/controller/controller.go
+++ b/cmd/registry/controller/controller.go
@@ -20,8 +20,7 @@ import (
 	"strings"
 	"time"
 
-	// "encoding/json"
-
+	"github.com/apigee/registry/cmd/registry/patterns"
 	"github.com/apigee/registry/connection"
 	"github.com/apigee/registry/log"
 	"github.com/apigee/registry/rpc"
@@ -109,8 +108,13 @@ func generateDependencyMap(
 
 	sourceMap := make(map[string]time.Time)
 
+	resourceName, err := patterns.ParseResourcePattern(resourcePattern)
+	if err != nil {
+		return nil, err
+	}
+
 	// Extend the dependency pattern if it contains $resource.api like pattern
-	extDependencyQuery, err := extendDependencyPattern(resourcePattern, dependency.Pattern, projectID)
+	extDependencyQuery, err := patterns.SubstituteReferenceEntity(dependency.Pattern, resourceName, projectID)
 	if err != nil {
 		return nil, err
 	}
@@ -122,7 +126,7 @@ func generateDependencyMap(
 	}
 
 	for _, source := range sourceList {
-		group, err := getEntityKey(dependency.Pattern, source.ResourceName())
+		group, err := patterns.GetReferenceEntityValue(dependency.Pattern, source.ResourceName())
 		if err != nil {
 			return nil, err
 		}
@@ -257,16 +261,16 @@ func generateCreateActions(
 	generatedResource *rpc.GeneratedResource,
 	visited map[string]bool) ([]*Action, error) {
 
-	var parentList []resourceInstance
+	var parentList []patterns.ResourceInstance
 
-	parsedResourcePattern, err := parseResourcePattern(resourcePattern)
+	parsedResourcePattern, err := patterns.ParseResourcePattern(resourcePattern)
 	if err != nil {
 		return nil, err
 	}
 
 	parentName := parsedResourcePattern.ParentName()
 	switch parentName.(type) {
-	case projectName:
+	case patterns.ProjectName:
 		// If parent is a project, we can't list projects since this is registry client command.
 		// Since the manifest definition is scoped  only for a particular project,
 		// there will be only one target resource in this case.
@@ -279,9 +283,9 @@ func generateCreateActions(
 		if visited[parentName.String()] {
 			return nil, nil
 		}
-		parentList = []resourceInstance{
-			projectResource{
-				projectName: parentName,
+		parentList = []patterns.ResourceInstance{
+			patterns.ProjectResource{
+				ProjectName: parentName,
 			},
 		}
 
@@ -298,7 +302,7 @@ func generateCreateActions(
 	for _, parent := range parentList {
 		// Since the GeneratedResource is non-existent here,
 		// we will have to derive the exact name of the target resource
-		targetResourceName, err := resourceNameFromParent(resourcePattern, parent.ResourceName().String())
+		targetResourceName, err := patterns.FullResourceNameFromParent(resourcePattern, parent.ResourceName().String())
 		if err != nil {
 			return nil, err
 		}
@@ -331,7 +335,7 @@ func generateCreateActions(
 }
 
 func needsUpdate(
-	targetResourceName resourceName,
+	targetResourceName patterns.ResourceName,
 	targetResourceTime time.Time,
 	dependencyMaps []map[string]time.Time,
 	generatedResource *rpc.GeneratedResource,
@@ -339,7 +343,7 @@ func needsUpdate(
 	for i, dependency := range generatedResource.Dependencies {
 		dMap := dependencyMaps[i]
 		// Get the entity to look for in dependencyMap
-		entityKey, err := getEntityKey(dependency.Pattern, targetResourceName)
+		entityKey, err := patterns.GetReferenceEntityValue(dependency.Pattern, targetResourceName)
 		if err != nil {
 			// This means that there is error in the pattern definition, hence return
 			return false, fmt.Errorf("cannot match resource with dependency. Error: %s", err.Error())
@@ -359,20 +363,20 @@ func needsUpdate(
 }
 
 func needsCreate(
-	targetResourceName resourceName,
+	targetResourceName patterns.ResourceName,
 	dependencyMaps []map[string]time.Time,
 	generatedResource *rpc.GeneratedResource) (bool, error) {
 	for i, dependency := range generatedResource.Dependencies {
 		dMap := dependencyMaps[i]
 		// Get the entity to look for in dependencyMap
-		entityKey, err := getEntityKey(dependency.Pattern, targetResourceName)
+		entityVal, err := patterns.GetReferenceEntityValue(dependency.Pattern, targetResourceName)
 		if err != nil {
 			// This means that there is error in the pattern definition, hence return
 			return false, fmt.Errorf("cannot match resource with dependency. Error: %s", err.Error())
 		}
 
 		// All the dependencies should be present to generate an action.
-		if _, ok := dMap[entityKey]; !ok {
+		if _, ok := dMap[entityVal]; !ok {
 			return false, nil
 		}
 	}
diff --git a/cmd/registry/controller/list.go b/cmd/registry/controller/list.go
index 85d8314..57b21df 100644
--- a/cmd/registry/controller/list.go
+++ b/cmd/registry/controller/list.go
@@ -18,13 +18,14 @@ import (
 	"context"
 
 	"github.com/apigee/registry/cmd/registry/core"
+	"github.com/apigee/registry/cmd/registry/patterns"
 	"github.com/apigee/registry/connection"
 	"github.com/apigee/registry/rpc"
 	"github.com/apigee/registry/server/registry/names"
 )
 
-func listResources(ctx context.Context, client connection.Client, pattern, filter string) ([]resourceInstance, error) {
-	var result []resourceInstance
+func listResources(ctx context.Context, client connection.Client, pattern, filter string) ([]patterns.ResourceInstance, error) {
+	var result []patterns.ResourceInstance
 	var err2 error
 
 	// First try to match collection names.
@@ -56,64 +57,64 @@ func listResources(ctx context.Context, client connection.Client, pattern, filte
 	return result, nil
 }
 
-func generateApiHandler(result *[]resourceInstance) func(*rpc.Api) error {
+func generateApiHandler(result *[]patterns.ResourceInstance) func(*rpc.Api) error {
 	return func(api *rpc.Api) error {
 		name, err := names.ParseApi(api.GetName())
 		if err != nil {
 			return err
 		}
 
-		(*result) = append((*result), apiResource{
-			apiName:         apiName{api: name},
-			updateTimestamp: api.UpdateTime.AsTime(),
+		(*result) = append((*result), patterns.ApiResource{
+			ApiName:   patterns.ApiName{Name: name},
+			Timestamp: api.UpdateTime.AsTime(),
 		})
 
 		return nil
 	}
 }
 
-func generateVersionHandler(result *[]resourceInstance) func(*rpc.ApiVersion) error {
+func generateVersionHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiVersion) error {
 	return func(version *rpc.ApiVersion) error {
 		name, err := names.ParseVersion(version.GetName())
 		if err != nil {
 			return err
 		}
 
-		(*result) = append((*result), versionResource{
-			versionName:     versionName{version: name},
-			updateTimestamp: version.UpdateTime.AsTime(),
+		(*result) = append((*result), patterns.VersionResource{
+			VersionName: patterns.VersionName{Name: name},
+			Timestamp:   version.UpdateTime.AsTime(),
 		})
 
 		return nil
 	}
 }
 
-func generateSpecHandler(result *[]resourceInstance) func(*rpc.ApiSpec) error {
+func generateSpecHandler(result *[]patterns.ResourceInstance) func(*rpc.ApiSpec) error {
 	return func(spec *rpc.ApiSpec) error {
 		name, err := names.ParseSpec(spec.GetName())
 		if err != nil {
 			return err
 		}
 
-		(*result) = append((*result), specResource{
-			specName:        specName{spec: name},
-			updateTimestamp: spec.RevisionUpdateTime.AsTime(),
+		(*result) = append((*result), patterns.SpecResource{
+			SpecName:  patterns.SpecName{Name: name},
+			Timestamp: spec.RevisionUpdateTime.AsTime(),
 		})
 
 		return nil
 	}
 }
 
-func generateArtifactHandler(result *[]resourceInstance) func(*rpc.Artifact) error {
+func generateArtifactHandler(result *[]patterns.ResourceInstance) func(*rpc.Artifact) error {
 	return func(artifact *rpc.Artifact) error {
 		name, err := names.ParseArtifact(artifact.GetName())
 		if err != nil {
 			return err
 		}
 
-		(*result) = append((*result), artifactResource{
-			artifactName:    artifactName{artifact: name},
-			updateTimestamp: artifact.UpdateTime.AsTime(),
+		(*result) = append((*result), patterns.ArtifactResource{
+			ArtifactName: patterns.ArtifactName{Name: name},
+			Timestamp:    artifact.UpdateTime.AsTime(),
 		})
 
 		return nil
diff --git a/cmd/registry/controller/parser.go b/cmd/registry/controller/parser.go
index 11338a6..658b241 100644
--- a/cmd/registry/controller/parser.go
+++ b/cmd/registry/controller/parser.go
@@ -20,148 +20,10 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/apigee/registry/cmd/registry/patterns"
 	"github.com/apigee/registry/rpc"
-	"github.com/apigee/registry/server/registry/names"
 )
 
-const resourceKW = "$resource"
-
-func parseResourceCollection(resourcePattern string) (resourceName, error) {
-	if project, err := names.ParseProjectCollection(resourcePattern); err == nil {
-		return projectName{project: project}, nil
-	} else if api, err := names.ParseApiCollection(resourcePattern); err == nil {
-		return apiName{api: api}, nil
-	} else if version, err := names.ParseVersionCollection(resourcePattern); err == nil {
-		return versionName{version: version}, nil
-	} else if spec, err := names.ParseSpecCollection(resourcePattern); err == nil {
-		return specName{spec: spec}, nil
-	} else if artifact, err := names.ParseArtifactCollection(resourcePattern); err == nil {
-		return artifactName{artifact: artifact}, nil
-	}
-
-	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
-}
-
-func parseResource(resourcePattern string) (resourceName, error) {
-	if project, err := names.ParseProject(resourcePattern); err == nil {
-		return projectName{project: project}, nil
-	} else if project, err := names.ParseProjectWithLocation(resourcePattern); err == nil {
-		return projectName{project: project}, nil
-	} else if api, err := names.ParseApi(resourcePattern); err == nil {
-		return apiName{api: api}, nil
-	} else if version, err := names.ParseVersion(resourcePattern); err == nil {
-		return versionName{version: version}, nil
-	} else if spec, err := names.ParseSpec(resourcePattern); err == nil {
-		return specName{spec: spec}, nil
-	} else if artifact, err := names.ParseArtifact(resourcePattern); err == nil {
-		return artifactName{artifact: artifact}, nil
-	}
-
-	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
-}
-
-func parseResourcePattern(resourcePattern string) (resourceName, error) {
-
-	// First try to match resource collections.
-	resource, err := parseResourceCollection(resourcePattern)
-	if err == nil {
-		return resource, nil
-	}
-
-	// Then try to match resource names.
-	resource, err = parseResource(resourcePattern)
-	if err == nil {
-		return resource, nil
-	}
-
-	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
-}
-
-func extendDependencyPattern(
-	resourcePattern string,
-	dependencyPattern string,
-	projectID string) (string, error) {
-	// Extends the source pattern by replacing references to $resource
-	// Example:
-	// resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-"
-	// dependencyPattern: "$resource.spec"
-	// Returns "projects/demo/locations/global/apis/-/versions/-/specs/-"
-
-	// resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-"
-	// dependencyPattern: "$resource.api/versions/-"
-	// Returns "projects/demo/locations/global/apis/-/versions/-"
-
-	// If there is no $resource prefix, prepend project name and return
-	if !strings.HasPrefix(dependencyPattern, resourceKW) {
-		return fmt.Sprintf("projects/%s/locations/global/%s", projectID, dependencyPattern), nil
-	}
-
-	// Extract the $resource reference
-	// Example result for the following regex
-	// dependencyPattern: "$resource.api/artifacts/score"
-	// matches: ["$resource.api/", "$resource.api", "api"]
-	entityRegex := regexp.MustCompile(fmt.Sprintf(`(\%s\.(api|version|spec|artifact))(/|$)`, resourceKW))
-	matches := entityRegex.FindStringSubmatch(dependencyPattern)
-	if len(matches) <= 2 {
-		return "", fmt.Errorf("invalid dependency pattern: %s", dependencyPattern)
-	}
-
-	// Convert resourcePattern to resourceName to extract entity values (api, spec, version,artifact)
-	resourceName, err := parseResourcePattern(resourcePattern)
-	if err != nil {
-		return "", err
-	}
-
-	entity, entityType := matches[1], matches[2]
-	entityVal := ""
-	switch entityType {
-	case "api":
-		entityVal = resourceName.Api()
-	case "version":
-		entityVal = resourceName.Version()
-	case "spec":
-		entityVal = resourceName.Spec()
-	case "artifact":
-		entityVal = resourceName.Artifact()
-	default:
-		return "", fmt.Errorf("invalid combination resourcePattern: %q dependencyPattern: %q", resourcePattern, dependencyPattern)
-	}
-
-	if len(entityVal) == 0 {
-		return "", fmt.Errorf("invalid combination resourcePattern: %q dependencyPattern: %q", resourcePattern, dependencyPattern)
-	}
-
-	return strings.Replace(dependencyPattern, entity, entityVal, 1), nil
-
-}
-
-func resourceNameFromParent(resourcePattern string, parent string) (resourceName, error) {
-	// Derives the resource name from the provided resourcePattern and it's parent.
-	// Example:
-	// 1) resourcePattern: projects/demo/locations/global/apis/-/versions/-/specs/openapi.yaml
-	//    parent: projects/demo/locations/global/apis/petstore/versions/1.0.0
-	//    returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
-	// 2) resourcePattern: projects/demo/locations/global/apis/petstore/versions/-/specs/-/artifacts/custom-artifact
-	//    parent: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
-	//    returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/custom-artifact
-
-	parsedResourcePattern, err := parseResourcePattern(resourcePattern)
-	if err != nil {
-		return nil, fmt.Errorf("Invalid target Pattern: %s", err)
-	}
-
-	// Replace the parent pattern in the resourcePattern with the supplied pattern name
-	resourceName := strings.Replace(resourcePattern, parsedResourcePattern.ParentName().String(), parent, 1)
-
-	//Validate generated resourceName
-	resource, err := parseResource(resourceName)
-	if err != nil {
-		return nil, fmt.Errorf("invalid pattern: %q cannot derive GeneratedResource name from parent %s", resourcePattern, parent)
-	}
-
-	return resource, nil
-}
-
 func ValidateManifest(ctx context.Context, parent string, manifest *rpc.Manifest) []error {
 	totalErrors := make([]error, 0)
 	for _, resource := range manifest.GeneratedResources {
@@ -174,7 +36,7 @@ func ValidateManifest(ctx context.Context, parent string, manifest *rpc.Manifest
 }
 
 func validateGeneratedResourceEntry(parent string, generatedResource *rpc.GeneratedResource) []error {
-	parsedTargetResource, err := parseResourcePattern(
+	parsedTargetResource, err := patterns.ParseResourcePattern(
 		fmt.Sprintf("%s/%s", parent, generatedResource.Pattern))
 
 	// Check that the target resource pattern should be a valid pattern.
@@ -192,7 +54,7 @@ func validateGeneratedResourceEntry(parent string, generatedResource *rpc.Genera
 		}
 	}
 
-	validateEntityReference := func(resourceName resourceName, entityType string) bool {
+	validateEntityReference := func(resourceName patterns.ResourceName, entityType string) bool {
 		switch entityType {
 		case "api":
 			return resourceName.Api() != ""
@@ -212,7 +74,7 @@ func validateGeneratedResourceEntry(parent string, generatedResource *rpc.Genera
 	errs := make([]error, 0)
 	for _, dependency := range generatedResource.Dependencies {
 		// Validate that all the dependencies have valid $resource references.
-		entityType, err := getEntityType(dependency.Pattern)
+		_, entityType, err := patterns.GetReferenceEntityType(dependency.Pattern)
 		if err != nil {
 			errs = append(errs, fmt.Errorf("invalid dependency pattern %s: %s", dependency.Pattern, err))
 		}
@@ -237,59 +99,6 @@ func validateGeneratedResourceEntry(parent string, generatedResource *rpc.Genera
 	return errs
 }
 
-func getEntityType(sourcePattern string) (string, error) {
-	// Reads the sourcePattern, finds out entity type in the $resource refernce
-	// Example:
-	// pattern: $resource.api/versions/-/specs/-
-	// returns "api", default if no reference is present
-
-	if !strings.HasPrefix(sourcePattern, resourceKW) {
-		return "default", nil
-	}
-
-	// Example:
-	// pattern: "$resource.api/versions/-/specs/-"
-	// re.FindStringSubmatch will return:
-	// ["$resource.api", "api"]
-	re := regexp.MustCompile(fmt.Sprintf(`\%s\.(api|version|spec|artifact)(/|$)`, resourceKW))
-
-	matches := re.FindStringSubmatch(sourcePattern)
-	if len(matches) <= 1 {
-		return "", fmt.Errorf("invalid pattern: Cannot extract entityType from pattern %s", sourcePattern)
-	}
-
-	return matches[1], nil
-}
-
-func getEntityKey(sourcePattern string, resource resourceName) (string, error) {
-	// Reads the sourcePattern and returns the entity value for the $resource reference
-	// Example:
-	// pattern: $resource.api/versions/-/specs/-
-	// resource: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"
-	// returns "projects/demo/locations/global/apis/petstore"
-
-	entityType, err := getEntityType(sourcePattern)
-	if err != nil {
-		return "", err
-	}
-
-	switch entityType {
-	case "api":
-		return resource.Api(), nil
-	case "version":
-		return resource.Version(), nil
-	case "spec":
-		return resource.Spec(), nil
-	case "artifact":
-		return resource.Artifact(), nil
-	case "default":
-		return "default", nil
-	default:
-		return "", fmt.Errorf("invalid pattern: Cannot extract entity from sourcePattern %s", sourcePattern)
-	}
-
-}
-
 type reference struct {
 	entity     string
 	entityType string
@@ -299,7 +108,7 @@ func getReferencesFromAction(action string) ([]*reference, error) {
 	references := make([]*reference, 0)
 
 	// Check if there is a reference to $resource in the action
-	isMatch, err := regexp.MatchString(fmt.Sprintf(`\%s`, resourceKW), action)
+	isMatch, err := regexp.MatchString(fmt.Sprintf(`\%s`, patterns.ResourceKW), action)
 	if err != nil {
 		return nil, err
 	}
@@ -311,7 +120,7 @@ func getReferencesFromAction(action string) ([]*reference, error) {
 	// Extract the $resource patterns from action
 	// action = "compute lintstats $resource.spec"
 	// This expression will match $resource.spec
-	re := regexp.MustCompile(fmt.Sprintf(`\%s\.(api|version|spec|artifact)($|/| )`, resourceKW))
+	re := regexp.MustCompile(fmt.Sprintf(`\%s\.(api|version|spec|artifact)($|/| )`, patterns.ResourceKW))
 	match := re.FindAllString(action, -1)
 	if len(match) == 0 {
 		return nil, fmt.Errorf("invalid action: %s missing or incorrect entity in the reference", action)
@@ -321,7 +130,7 @@ func getReferencesFromAction(action string) ([]*reference, error) {
 	for _, m := range match {
 		// entity = $resource.api, extract the entityType as "api"
 		entity := strings.TrimRight(m, " /")
-		entityType := entity[len(resourceKW)+1:]
+		entityType := entity[len(patterns.ResourceKW)+1:]
 		references = append(references, &reference{entity: entity, entityType: entityType})
 	}
 
@@ -340,7 +149,7 @@ func generateCommand(action string, resourceName string) (string, error) {
 		return action, nil
 	}
 
-	resource, err := parseResource(resourceName)
+	resource, err := patterns.ParseResourcePattern(resourceName)
 	if err != nil {
 		return "", fmt.Errorf("error generating command, invalid resourceName: %s", resourceName)
 	}
diff --git a/cmd/registry/controller/resource-name.go b/cmd/registry/controller/resource-name.go
deleted file mode 100644
index 96859a4..0000000
--- a/cmd/registry/controller/resource-name.go
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package controller
-
-import (
-	"github.com/apigee/registry/server/registry/names"
-)
-
-// This interface is used to describe resource patterns
-// Example:
-// projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
-// projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-
-type resourceName interface {
-	Artifact() string
-	Spec() string
-	Version() string
-	Api() string
-	String() string
-	ParentName() resourceName
-}
-
-type specName struct {
-	spec names.Spec
-}
-
-func (s specName) Artifact() string {
-	return ""
-}
-
-func (s specName) Spec() string {
-	return s.spec.String()
-}
-
-func (s specName) Version() string {
-	return s.spec.Version().String()
-}
-
-func (s specName) Api() string {
-	return s.spec.Api().String()
-}
-
-func (s specName) String() string {
-	return s.spec.String()
-}
-
-func (s specName) ParentName() resourceName {
-	// Validate the parent name aand return
-	if version, err := names.ParseVersion(s.spec.Parent()); err == nil {
-		return versionName{
-			version: version,
-		}
-	} else if version, err := names.ParseVersionCollection(s.spec.Parent()); err == nil {
-		return versionName{
-			version: version,
-		}
-	}
-	return versionName{}
-}
-
-type versionName struct {
-	version names.Version
-}
-
-func (v versionName) Artifact() string {
-	return ""
-}
-
-func (v versionName) Spec() string {
-	return ""
-}
-
-func (v versionName) Version() string {
-	return v.version.String()
-}
-
-func (v versionName) Api() string {
-	return v.version.Api().String()
-}
-
-func (v versionName) String() string {
-	return v.version.String()
-}
-
-func (v versionName) ParentName() resourceName {
-	// Validate the parent name aand return
-	if api, err := names.ParseApi(v.version.Parent()); err == nil {
-		return apiName{
-			api: api,
-		}
-	} else if api, err := names.ParseApiCollection(v.version.Parent()); err == nil {
-		return apiName{
-			api: api,
-		}
-	}
-
-	return apiName{}
-}
-
-type apiName struct {
-	api names.Api
-}
-
-func (a apiName) Artifact() string {
-	return ""
-}
-
-func (a apiName) Spec() string {
-	return ""
-}
-
-func (a apiName) Version() string {
-	return ""
-}
-
-func (a apiName) Api() string {
-	return a.api.String()
-}
-
-func (a apiName) String() string {
-	return a.api.String()
-}
-
-func (a apiName) ParentName() resourceName {
-	// Validate the parent name aand return
-	if project, err := names.ParseProject(a.api.Parent()); err == nil {
-		return projectName{
-			project: project,
-		}
-	} else if project, err := names.ParseProjectWithLocation(a.api.Parent()); err == nil {
-		return projectName{
-			project: project,
-		}
-	} else if project, err := names.ParseProjectCollection(a.api.Parent()); err == nil {
-		return projectName{
-			project: project,
-		}
-	}
-
-	return projectName{}
-}
-
-type projectName struct {
-	project names.Project
-}
-
-func (p projectName) Artifact() string {
-	return ""
-}
-
-func (p projectName) Spec() string {
-	return ""
-}
-
-func (p projectName) Version() string {
-	return ""
-}
-
-func (p projectName) Api() string {
-	return ""
-}
-
-func (p projectName) String() string {
-	return p.project.String()
-}
-
-func (p projectName) ParentName() resourceName {
-	return nil
-}
-
-type artifactName struct {
-	artifact names.Artifact
-}
-
-func (ar artifactName) Artifact() string {
-	return ar.artifact.String()
-}
-
-func (ar artifactName) Spec() string {
-	specPattern := names.Spec{
-		ProjectID: ar.artifact.ProjectID(),
-		ApiID:     ar.artifact.ApiID(),
-		VersionID: ar.artifact.VersionID(),
-		SpecID:    ar.artifact.SpecID(),
-	}
-
-	// Validate the generated name
-	if spec, err := names.ParseSpec(specPattern.String()); err == nil {
-		return spec.String()
-	} else if _, err := names.ParseSpecCollection(specPattern.String()); err == nil {
-		return spec.String()
-	}
-
-	return ""
-}
-
-func (ar artifactName) Version() string {
-	versionPattern := names.Version{
-		ProjectID: ar.artifact.ProjectID(),
-		ApiID:     ar.artifact.ApiID(),
-		VersionID: ar.artifact.VersionID(),
-	}
-	// Validate the generated name
-	if version, err := names.ParseVersion(versionPattern.String()); err == nil {
-		return version.String()
-	} else if _, err := names.ParseVersionCollection(versionPattern.String()); err == nil {
-		return version.String()
-	}
-
-	return ""
-}
-
-func (ar artifactName) Api() string {
-	apiPattern := names.Api{
-		ProjectID: ar.artifact.ProjectID(),
-		ApiID:     ar.artifact.ApiID(),
-	}
-	// Validate the generated name
-	if _, err := names.ParseApi(apiPattern.String()); err == nil {
-		return apiPattern.String()
-	} else if _, err := names.ParseApiCollection(apiPattern.String()); err == nil {
-		return apiPattern.String()
-	}
-
-	return ""
-}
-
-func (ar artifactName) String() string {
-	return ar.artifact.String()
-}
-
-func (ar artifactName) ParentName() resourceName {
-	parent := ar.artifact.Parent()
-	// First try to match collection names.
-	if project, err := names.ParseProjectCollection(parent); err == nil {
-		return projectName{
-			project: project,
-		}
-	} else if api, err := names.ParseApiCollection(parent); err == nil {
-		return apiName{
-			api: api,
-		}
-	} else if version, err := names.ParseVersionCollection(parent); err == nil {
-		return versionName{
-			version: version,
-		}
-	} else if spec, err := names.ParseSpecCollection(parent); err == nil {
-		return specName{
-			spec: spec,
-		}
-	}
-
-	// Then try to match resource names.
-	if project, err := names.ParseProject(parent); err == nil {
-		return projectName{
-			project: project,
-		}
-	} else if project, err := names.ParseProjectWithLocation(parent); err == nil {
-		return projectName{
-			project: project,
-		}
-	} else if api, err := names.ParseApi(parent); err == nil {
-		return apiName{
-			api: api,
-		}
-	} else if version, err := names.ParseVersion(parent); err == nil {
-		return versionName{
-			version: version,
-		}
-	} else if spec, err := names.ParseSpec(parent); err == nil {
-		return specName{
-			spec: spec,
-		}
-	}
-
-	return nil
-}
diff --git a/cmd/registry/controller/resource.go b/cmd/registry/controller/resource.go
deleted file mode 100644
index c45550d..0000000
--- a/cmd/registry/controller/resource.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package controller
-
-import (
-	"time"
-)
-
-// This interface is used to describe resource instances
-// ResourceName is embeded, the only additional field is the UpdateTimestamp
-type resourceInstance interface {
-	ResourceName() resourceName
-	UpdateTimestamp() time.Time
-}
-
-type specResource struct {
-	specName        resourceName
-	updateTimestamp time.Time
-}
-
-func (s specResource) UpdateTimestamp() time.Time {
-	return s.updateTimestamp
-}
-
-func (s specResource) ResourceName() resourceName {
-	return s.specName
-}
-
-type versionResource struct {
-	versionName     resourceName
-	updateTimestamp time.Time
-}
-
-func (v versionResource) UpdateTimestamp() time.Time {
-	return v.updateTimestamp
-}
-
-func (v versionResource) ResourceName() resourceName {
-	return v.versionName
-}
-
-type apiResource struct {
-	apiName         resourceName
-	updateTimestamp time.Time
-}
-
-func (a apiResource) UpdateTimestamp() time.Time {
-	return a.updateTimestamp
-}
-
-func (a apiResource) ResourceName() resourceName {
-	return a.apiName
-}
-
-type projectResource struct {
-	projectName     resourceName
-	updateTimestamp time.Time
-}
-
-func (p projectResource) UpdateTimestamp() time.Time {
-	return p.updateTimestamp
-}
-
-func (p projectResource) ResourceName() resourceName {
-	return p.projectName
-}
-
-type artifactResource struct {
-	artifactName    resourceName
-	updateTimestamp time.Time
-}
-
-func (ar artifactResource) UpdateTimestamp() time.Time {
-	return ar.updateTimestamp
-}
-
-func (ar artifactResource) ResourceName() resourceName {
-	return ar.artifactName
-}
diff --git a/cmd/registry/core/print.go b/cmd/registry/core/print.go
index f3f1eda..11ec836 100644
--- a/cmd/registry/core/print.go
+++ b/cmd/registry/core/print.go
@@ -135,6 +135,14 @@ func PrintArtifactContents(artifact *rpc.Artifact) error {
 		return unmarshalAndPrint(artifact.GetContents(), &rpc.Receipt{})
 	case "google.cloud.apigeeregistry.applications.v1alpha1.References":
 		return unmarshalAndPrint(artifact.GetContents(), &rpc.References{})
+	case "google.cloud.apigeeregistry.v1.scoring.Score":
+		return unmarshalAndPrint(artifact.GetContents(), &rpc.Score{})
+	case "google.cloud.apigeeregistry.v1.scoring.ScoreCard":
+		return unmarshalAndPrint(artifact.GetContents(), &rpc.ScoreCard{})
+	case "google.cloud.apigeeregistry.v1.scoring.ScoreDefinition":
+		return unmarshalAndPrint(artifact.GetContents(), &rpc.ScoreDefinition{})
+	case "google.cloud.apigeeregistry.v1.scoring.ScoreCardDefinition":
+		return unmarshalAndPrint(artifact.GetContents(), &rpc.ScoreCardDefinition{})
 	case "google.cloud.apigeeregistry.applications.v1alpha1.StyleGuide":
 		return unmarshalAndPrint(artifact.GetContents(), &rpc.StyleGuide{})
 	case "gnostic.openapiv2.Document":
diff --git a/cmd/registry/patch/artifact_score.go b/cmd/registry/patch/artifact_score.go
new file mode 100644
index 0000000..df8b22a
--- /dev/null
+++ b/cmd/registry/patch/artifact_score.go
@@ -0,0 +1,19 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package patch
+
+const ScoreMimeType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.Score"
+
+// TODO: Add logic for yaml uploads
diff --git a/cmd/registry/patch/artifact_score_definition.go b/cmd/registry/patch/artifact_score_definition.go
new file mode 100644
index 0000000..28e6907
--- /dev/null
+++ b/cmd/registry/patch/artifact_score_definition.go
@@ -0,0 +1,19 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package patch
+
+const ScoreDefinitionMimeType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.ScoreDefinition"
+
+// TODO: Add logic for yaml uploads
diff --git a/cmd/registry/patch/artifact_scorecard.go b/cmd/registry/patch/artifact_scorecard.go
new file mode 100644
index 0000000..89756e9
--- /dev/null
+++ b/cmd/registry/patch/artifact_scorecard.go
@@ -0,0 +1,19 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package patch
+
+const ScoreCardMimeType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.ScoreCard"
+
+// TODO: Add logic for yaml uploads
diff --git a/cmd/registry/patch/artifact_scorecard_definition.go b/cmd/registry/patch/artifact_scorecard_definition.go
new file mode 100644
index 0000000..2ecc847
--- /dev/null
+++ b/cmd/registry/patch/artifact_scorecard_definition.go
@@ -0,0 +1,19 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package patch
+
+const ScoreCardDefinitionMimeType = "application/octet-stream;type=google.cloud.apigeeregistry.v1.scoring.ScoreCardDefinition"
+
+// TODO: Add logic for yaml uploads
diff --git a/cmd/registry/patterns/parser.go b/cmd/registry/patterns/parser.go
new file mode 100644
index 0000000..22f79f7
--- /dev/null
+++ b/cmd/registry/patterns/parser.go
@@ -0,0 +1,195 @@
+package patterns
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"github.com/apigee/registry/server/registry/names"
+)
+
+const ResourceKW = "$resource"
+
+func parseResourceCollection(resourcePattern string) (ResourceName, error) {
+	if project, err := names.ParseProjectCollection(resourcePattern); err == nil {
+		return ProjectName{Name: project}, nil
+	} else if api, err := names.ParseApiCollection(resourcePattern); err == nil {
+		return ApiName{Name: api}, nil
+	} else if version, err := names.ParseVersionCollection(resourcePattern); err == nil {
+		return VersionName{Name: version}, nil
+	} else if spec, err := names.ParseSpecCollection(resourcePattern); err == nil {
+		return SpecName{Name: spec}, nil
+	} else if artifact, err := names.ParseArtifactCollection(resourcePattern); err == nil {
+		return ArtifactName{Name: artifact}, nil
+	}
+
+	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
+}
+
+func parseResource(resourcePattern string) (ResourceName, error) {
+	if project, err := names.ParseProject(resourcePattern); err == nil {
+		return ProjectName{Name: project}, nil
+	} else if project, err := names.ParseProjectWithLocation(resourcePattern); err == nil {
+		return ProjectName{Name: project}, nil
+	} else if api, err := names.ParseApi(resourcePattern); err == nil {
+		return ApiName{Name: api}, nil
+	} else if version, err := names.ParseVersion(resourcePattern); err == nil {
+		return VersionName{Name: version}, nil
+	} else if spec, err := names.ParseSpec(resourcePattern); err == nil {
+		return SpecName{Name: spec}, nil
+	} else if artifact, err := names.ParseArtifact(resourcePattern); err == nil {
+		return ArtifactName{Name: artifact}, nil
+	}
+
+	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
+}
+
+func ParseResourcePattern(resourcePattern string) (ResourceName, error) {
+
+	// First try to match resource collections.
+	resource, err := parseResourceCollection(resourcePattern)
+	if err == nil {
+		return resource, nil
+	}
+
+	// Then try to match resource names.
+	resource, err = parseResource(resourcePattern)
+	if err == nil {
+		return resource, nil
+	}
+
+	return nil, fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
+}
+
+func SubstituteReferenceEntity(resourcePattern string, referred ResourceName, projectID string) (string, error) {
+	// Extends the resource pattern by replacing references to $resource
+	// Example:
+	// referred: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-"
+	// resourcePattern: "$resource.spec"
+	// Returns "projects/demo/locations/global/apis/-/versions/-/specs/-"
+
+	// referred: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-"
+	// resourcePattern: "$resource.api/versions/-"
+	// Returns "projects/demo/locations/global/apis/-/versions/-"
+
+	entity, entityType, err := GetReferenceEntityType(resourcePattern)
+	if err != nil {
+		return "", err
+	}
+
+	// no $resource reference present
+	// simply prepend the projectname and return full resource name
+	if entityType == "default" {
+		return fmt.Sprintf("projects/%s/locations/global/%s", projectID, resourcePattern), nil
+	}
+
+	entityVal, err := GetReferenceEntityValue(resourcePattern, referred)
+	if err != nil {
+		return "", err
+	}
+
+	return strings.Replace(resourcePattern, entity, entityVal, 1), nil
+
+}
+
+func GetReferenceEntityType(resourcePattern string) (entity, entityType string, err error) {
+	// Reads the resourcePattern, finds out entity type in the $resource refernce
+	// Example:
+	// pattern: $resource.api/versions/-/specs/-
+	// returns "$resource.api","api"
+	// return "", "default" if no reference is present
+
+	if !strings.HasPrefix(resourcePattern, ResourceKW) {
+		entity, entityType = "", "default"
+		err = nil
+		return
+	}
+
+	// Extract the $resource reference
+	// Example result for the following regex
+	// dependencyPattern: "$resource.api/artifacts/score"
+	// matches: ["$resource.api/", "$resource.api", "api"]
+	entityRegex := regexp.MustCompile(fmt.Sprintf(`(\%s\.(api|version|spec|artifact))(/|$)`, ResourceKW))
+	matches := entityRegex.FindStringSubmatch(resourcePattern)
+	if len(matches) <= 2 {
+		entity, entityType = "", ""
+		err = fmt.Errorf("invalid resourcePattern: %s", resourcePattern)
+		return
+	}
+
+	entity, entityType = matches[1], matches[2]
+	err = nil
+	return
+}
+
+func GetReferenceEntityValue(resourcePattern string, referred ResourceName) (string, error) {
+	// Reads the sourcePattern and returns the entity value for the $resource reference
+	// Example:
+	// pattern: $resource.api/versions/-/specs/-
+	// resource: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"
+	// returns "projects/demo/locations/global/apis/petstore"
+
+	_, entityType, err := GetReferenceEntityType(resourcePattern)
+	if err != nil {
+		return "", err
+	}
+
+	switch entityType {
+	case "api":
+		entityVal := referred.Api()
+		if len(entityVal) == 0 {
+			return "", fmt.Errorf("invalid combination referred: %q resourcePattern: %q", referred, resourcePattern)
+		}
+		return entityVal, nil
+	case "version":
+		entityVal := referred.Version()
+		if len(entityVal) == 0 {
+			return "", fmt.Errorf("invalid combination referred: %q resourcePattern: %q", referred, resourcePattern)
+		}
+		return entityVal, nil
+	case "spec":
+		entityVal := referred.Spec()
+		if len(entityVal) == 0 {
+			return "", fmt.Errorf("invalid combination referred: %q resourcePattern: %q", referred, resourcePattern)
+		}
+		return entityVal, nil
+	case "artifact":
+		entityVal := referred.Artifact()
+		if len(entityVal) == 0 {
+			return "", fmt.Errorf("invalid combination referred: %q resourcePattern: %q", referred, resourcePattern)
+		}
+		return entityVal, nil
+	case "default":
+		return "default", nil
+	default:
+		return "", fmt.Errorf("invalid combination referred: %q resourcePattern: %q", referred, resourcePattern)
+	}
+
+}
+
+func FullResourceNameFromParent(resourcePattern string, parent string) (ResourceName, error) {
+	// Derives the resource name from the provided resourcePattern and it's parent.
+	// Example:
+	// 1) resourcePattern: projects/demo/locations/global/apis/-/versions/-/specs/openapi.yaml
+	//    parent: projects/demo/locations/global/apis/petstore/versions/1.0.0
+	//    returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
+	// 2) resourcePattern: projects/demo/locations/global/apis/petstore/versions/-/specs/-/artifacts/custom-artifact
+	//    parent: projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
+	//    returns projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/custom-artifact
+
+	parsedResourcePattern, err := ParseResourcePattern(resourcePattern)
+	if err != nil {
+		return nil, fmt.Errorf("Invalid target Pattern: %s", err)
+	}
+
+	// Replace the parent pattern in the resourcePattern with the supplied pattern name
+	resourceName := strings.Replace(resourcePattern, parsedResourcePattern.ParentName().String(), parent, 1)
+
+	//Validate generated resourceName
+	resource, err := parseResource(resourceName)
+	if err != nil {
+		return nil, fmt.Errorf("invalid pattern: %q cannot derive GeneratedResource name from parent %s", resourcePattern, parent)
+	}
+
+	return resource, nil
+}
diff --git a/cmd/registry/patterns/resources.go b/cmd/registry/patterns/resources.go
new file mode 100644
index 0000000..fa063a3
--- /dev/null
+++ b/cmd/registry/patterns/resources.go
@@ -0,0 +1,362 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package patterns
+
+import (
+	"time"
+
+	"github.com/apigee/registry/server/registry/names"
+)
+
+// This interface is used to describe generic resource names
+// Example:
+// projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml
+// projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-
+type ResourceName interface {
+	Artifact() string
+	Spec() string
+	Version() string
+	Api() string
+	String() string
+	ParentName() ResourceName
+}
+
+type SpecName struct {
+	Name names.Spec
+}
+
+func (s SpecName) Artifact() string {
+	return ""
+}
+
+func (s SpecName) Spec() string {
+	return s.Name.String()
+}
+
+func (s SpecName) Version() string {
+	return s.Name.Version().String()
+}
+
+func (s SpecName) Api() string {
+	return s.Name.Api().String()
+}
+
+func (s SpecName) String() string {
+	return s.Name.String()
+}
+
+func (s SpecName) ParentName() ResourceName {
+	// Validate the parent name aand return
+	if version, err := names.ParseVersion(s.Name.Parent()); err == nil {
+		return VersionName{
+			Name: version,
+		}
+	} else if version, err := names.ParseVersionCollection(s.Name.Parent()); err == nil {
+		return VersionName{
+			Name: version,
+		}
+	}
+	return VersionName{}
+}
+
+type VersionName struct {
+	Name names.Version
+}
+
+func (v VersionName) Artifact() string {
+	return ""
+}
+
+func (v VersionName) Spec() string {
+	return ""
+}
+
+func (v VersionName) Version() string {
+	return v.Name.String()
+}
+
+func (v VersionName) Api() string {
+	return v.Name.Api().String()
+}
+
+func (v VersionName) String() string {
+	return v.Name.String()
+}
+
+func (v VersionName) ParentName() ResourceName {
+	// Validate the parent name aand return
+	if api, err := names.ParseApi(v.Name.Parent()); err == nil {
+		return ApiName{
+			Name: api,
+		}
+	} else if api, err := names.ParseApiCollection(v.Name.Parent()); err == nil {
+		return ApiName{
+			Name: api,
+		}
+	}
+
+	return ApiName{}
+}
+
+type ApiName struct {
+	Name names.Api
+}
+
+func (a ApiName) Artifact() string {
+	return ""
+}
+
+func (a ApiName) Spec() string {
+	return ""
+}
+
+func (a ApiName) Version() string {
+	return ""
+}
+
+func (a ApiName) Api() string {
+	return a.Name.String()
+}
+
+func (a ApiName) String() string {
+	return a.Name.String()
+}
+
+func (a ApiName) ParentName() ResourceName {
+	// Validate the parent name aand return
+	if project, err := names.ParseProject(a.Name.Parent()); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	} else if project, err := names.ParseProjectWithLocation(a.Name.Parent()); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	} else if project, err := names.ParseProjectCollection(a.Name.Parent()); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	}
+
+	return ProjectName{}
+}
+
+type ProjectName struct {
+	Name names.Project
+}
+
+func (p ProjectName) Artifact() string {
+	return ""
+}
+
+func (p ProjectName) Spec() string {
+	return ""
+}
+
+func (p ProjectName) Version() string {
+	return ""
+}
+
+func (p ProjectName) Api() string {
+	return ""
+}
+
+func (p ProjectName) String() string {
+	return p.Name.String()
+}
+
+func (p ProjectName) ParentName() ResourceName {
+	return nil
+}
+
+type ArtifactName struct {
+	Name names.Artifact
+}
+
+func (ar ArtifactName) Artifact() string {
+	return ar.Name.String()
+}
+
+func (ar ArtifactName) Spec() string {
+	specPattern := names.Spec{
+		ProjectID: ar.Name.ProjectID(),
+		ApiID:     ar.Name.ApiID(),
+		VersionID: ar.Name.VersionID(),
+		SpecID:    ar.Name.SpecID(),
+	}
+
+	// Validate the generated name
+	if spec, err := names.ParseSpec(specPattern.String()); err == nil {
+		return spec.String()
+	} else if _, err := names.ParseSpecCollection(specPattern.String()); err == nil {
+		return spec.String()
+	}
+
+	return ""
+}
+
+func (ar ArtifactName) Version() string {
+	versionPattern := names.Version{
+		ProjectID: ar.Name.ProjectID(),
+		ApiID:     ar.Name.ApiID(),
+		VersionID: ar.Name.VersionID(),
+	}
+	// Validate the generated name
+	if version, err := names.ParseVersion(versionPattern.String()); err == nil {
+		return version.String()
+	} else if _, err := names.ParseVersionCollection(versionPattern.String()); err == nil {
+		return version.String()
+	}
+
+	return ""
+}
+
+func (ar ArtifactName) Api() string {
+	apiPattern := names.Api{
+		ProjectID: ar.Name.ProjectID(),
+		ApiID:     ar.Name.ApiID(),
+	}
+	// Validate the generated name
+	if _, err := names.ParseApi(apiPattern.String()); err == nil {
+		return apiPattern.String()
+	} else if _, err := names.ParseApiCollection(apiPattern.String()); err == nil {
+		return apiPattern.String()
+	}
+
+	return ""
+}
+
+func (ar ArtifactName) String() string {
+	return ar.Name.String()
+}
+
+func (ar ArtifactName) ParentName() ResourceName {
+	parent := ar.Name.Parent()
+	// First try to match collection names.
+	if project, err := names.ParseProjectCollection(parent); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	} else if api, err := names.ParseApiCollection(parent); err == nil {
+		return ApiName{
+			Name: api,
+		}
+	} else if version, err := names.ParseVersionCollection(parent); err == nil {
+		return VersionName{
+			Name: version,
+		}
+	} else if spec, err := names.ParseSpecCollection(parent); err == nil {
+		return SpecName{
+			Name: spec,
+		}
+	}
+
+	// Then try to match resource names.
+	if project, err := names.ParseProject(parent); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	} else if project, err := names.ParseProjectWithLocation(parent); err == nil {
+		return ProjectName{
+			Name: project,
+		}
+	} else if api, err := names.ParseApi(parent); err == nil {
+		return ApiName{
+			Name: api,
+		}
+	} else if version, err := names.ParseVersion(parent); err == nil {
+		return VersionName{
+			Name: version,
+		}
+	} else if spec, err := names.ParseSpec(parent); err == nil {
+		return SpecName{
+			Name: spec,
+		}
+	}
+
+	return nil
+}
+
+// This interface is used to describe generic resource instances
+// ResourceName is embeded, the only additional field is the UpdateTimestamp
+type ResourceInstance interface {
+	ResourceName() ResourceName
+	UpdateTimestamp() time.Time
+}
+
+type SpecResource struct {
+	SpecName  ResourceName
+	Timestamp time.Time
+}
+
+func (s SpecResource) UpdateTimestamp() time.Time {
+	return s.Timestamp
+}
+
+func (s SpecResource) ResourceName() ResourceName {
+	return s.SpecName
+}
+
+type VersionResource struct {
+	VersionName ResourceName
+	Timestamp   time.Time
+}
+
+func (v VersionResource) UpdateTimestamp() time.Time {
+	return v.Timestamp
+}
+
+func (v VersionResource) ResourceName() ResourceName {
+	return v.VersionName
+}
+
+type ApiResource struct {
+	ApiName   ResourceName
+	Timestamp time.Time
+}
+
+func (a ApiResource) UpdateTimestamp() time.Time {
+	return a.Timestamp
+}
+
+func (a ApiResource) ResourceName() ResourceName {
+	return a.ApiName
+}
+
+type ProjectResource struct {
+	ProjectName ResourceName
+	Timestamp   time.Time
+}
+
+func (p ProjectResource) UpdateTimestamp() time.Time {
+	return p.Timestamp
+}
+
+func (p ProjectResource) ResourceName() ResourceName {
+	return p.ProjectName
+}
+
+type ArtifactResource struct {
+	ArtifactName ResourceName
+	Timestamp    time.Time
+}
+
+func (ar ArtifactResource) UpdateTimestamp() time.Time {
+	return ar.Timestamp
+}
+
+func (ar ArtifactResource) ResourceName() ResourceName {
+	return ar.ArtifactName
+}


Test patch:
diff --git a/cmd/registry/cmd/upload/artifact_test.go b/cmd/registry/cmd/upload/artifact_test.go
index 8a37226..7c9b789 100644
--- a/cmd/registry/cmd/upload/artifact_test.go
+++ b/cmd/registry/cmd/upload/artifact_test.go
@@ -29,6 +29,34 @@ import (
 	"google.golang.org/protobuf/testing/protocmp"
 )
 
+func SetUpProject(ctx context.Context, t *testing.T, project string) {
+	t.Helper()
+
+	adminClient, err := connection.NewAdminClient(ctx)
+	if err != nil {
+		t.Fatalf("Setup: Failed to create client: %s", err)
+	}
+
+	err = adminClient.DeleteProject(ctx, &rpc.DeleteProjectRequest{
+		Name:  "projects/" + project,
+		Force: true,
+	})
+	if err != nil && status.Code(err) != codes.NotFound {
+		t.Fatalf("Setup: Failed to delete test project: %s", err)
+	}
+
+	_, err = adminClient.CreateProject(ctx, &rpc.CreateProjectRequest{
+		ProjectId: project,
+		Project: &rpc.Project{
+			DisplayName: "Demo",
+			Description: "A demo catalog",
+		},
+	})
+	if err != nil {
+		t.Fatalf("Failed to create project %s: %s", project, err.Error())
+	}
+}
+
 func TestManifestArtifactUpload(t *testing.T) {
 	tests := []struct {
 		desc     string
@@ -62,32 +90,180 @@ func TestManifestArtifactUpload(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.desc, func(t *testing.T) {
 			ctx := context.Background()
+
+			SetUpProject(ctx, t, test.project)
+
 			client, err := connection.NewClient(ctx)
 			if err != nil {
 				t.Fatalf("Setup: Failed to create client: %s", err)
 			}
-			adminClient, err := connection.NewAdminClient(ctx)
+
+			cmd := Command()
+			args := []string{"artifact", test.filePath, "--parent", fmt.Sprintf("projects/%s/locations/global", test.project)}
+			cmd.SetArgs(args)
+			if err = cmd.Execute(); err != nil {
+				t.Fatalf("Execute() with args %v returned error: %s", args, err)
+			}
+
+			req := &rpc.GetArtifactContentsRequest{
+				Name: "projects/" + test.project + "/locations/global/artifacts/test-manifest",
+			}
+
+			manifest := &rpc.Manifest{}
+			body, err := client.GetArtifactContents(ctx, req)
+			if err != nil {
+				t.Fatalf("GetArtifactContents() returned error: %s", err)
+			}
+			contents := body.GetData()
+			err = proto.Unmarshal(contents, manifest)
+			if err != nil {
+				t.Fatalf("proto.Unmarshal() returned error: %s", err)
+			}
+
+			// Verify the manifest definition is correct
+			opts := cmp.Options{
+				protocmp.Transform(),
+			}
+
+			if diff := cmp.Diff(test.want, manifest, opts); diff != "" {
+				t.Errorf("GetArtifactContents returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestScoreDefinitionArtifactUpload(t *testing.T) {
+	tests := []struct {
+		desc     string
+		project  string
+		filePath string
+		want     *rpc.ScoreDefinition
+	}{
+		{
+			desc:     "simple score definition artifact upload",
+			project:  "upload-score-definition-artifact-demo",
+			filePath: filepath.Join("testdata", "score-definition.yaml"),
+			want: &rpc.ScoreDefinition{
+				Id:   "test-score-definition",
+				Kind: "ScoreDefinition",
+				TargetResource: &rpc.ResourcePattern{
+					Pattern: "apis/-/versions/-/specs/-",
+				},
+				Formula: &rpc.ScoreDefinition_ScoreFormula{
+					ScoreFormula: &rpc.ScoreFormula{
+						Pattern: &rpc.ResourcePattern{
+							Pattern: "$resource.spec/artifacts/conformance-report",
+						},
+						ScoreExpression: "size(conformance.GuidelineReportGroups[2].RuleReportGroups[1])",
+					},
+				},
+				Type: &rpc.ScoreDefinition_Integer{
+					Integer: &rpc.IntegerType{
+						MinValue: 0,
+						MaxValue: 100,
+						Thresholds: []*rpc.NumberThreshold{
+							{
+								Severity: rpc.Severity_ALERT,
+								Range: &rpc.NumberThreshold_NumberRange{
+									Min: 60,
+									Max: 100,
+								},
+							},
+							{
+								Severity: rpc.Severity_OK,
+								Range: &rpc.NumberThreshold_NumberRange{
+									Min: 0,
+									Max: 59,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			ctx := context.Background()
+
+			SetUpProject(ctx, t, test.project)
+
+			client, err := connection.NewClient(ctx)
 			if err != nil {
 				t.Fatalf("Setup: Failed to create client: %s", err)
 			}
 
-			err = adminClient.DeleteProject(ctx, &rpc.DeleteProjectRequest{
-				Name:  "projects/" + test.project,
-				Force: true,
-			})
-			if err != nil && status.Code(err) != codes.NotFound {
-				t.Fatalf("Setup: Failed to delete test project: %s", err)
+			cmd := Command()
+			args := []string{"artifact", test.filePath, "--parent", fmt.Sprintf("projects/%s/locations/global", test.project)}
+			cmd.SetArgs(args)
+			if err = cmd.Execute(); err != nil {
+				t.Fatalf("Execute() with args %v returned error: %s", args, err)
+			}
+
+			req := &rpc.GetArtifactContentsRequest{
+				Name: "projects/" + test.project + "/locations/global/artifacts/test-score-definition",
+			}
+
+			definition := &rpc.ScoreDefinition{}
+			body, err := client.GetArtifactContents(ctx, req)
+			if err != nil {
+				t.Fatalf("GetArtifactContents() returned error: %s", err)
+			}
+			contents := body.GetData()
+			err = proto.Unmarshal(contents, definition)
+			if err != nil {
+				t.Fatalf("proto.Unmarshal() returned error: %s", err)
+			}
+
+			// Verify the manifest definition is correct
+			opts := cmp.Options{
+				protocmp.Transform(),
 			}
 
-			_, err = adminClient.CreateProject(ctx, &rpc.CreateProjectRequest{
-				ProjectId: test.project,
-				Project: &rpc.Project{
-					DisplayName: "Demo",
-					Description: "A demo catalog",
+			if diff := cmp.Diff(test.want, definition, opts); diff != "" {
+				t.Errorf("GetArtifactContents returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestScoreCardDefinitionArtifactUpload(t *testing.T) {
+	tests := []struct {
+		desc     string
+		project  string
+		filePath string
+		want     *rpc.ScoreCardDefinition
+	}{
+		{
+			desc:     "simple scorecard definition artifact upload",
+			project:  "upload-scorecard-definition-artifact-demo",
+			filePath: filepath.Join("testdata", "scorecard-definition.yaml"),
+			want: &rpc.ScoreCardDefinition{
+				Id:   "test-scorecard-definition",
+				Kind: "ScoreCardDefinition",
+				TargetResource: &rpc.ResourcePattern{
+					Pattern: "apis/-/versions/-/specs/-",
+				},
+				ScorePatterns: []string{
+					"$resource.spec/artifacts/score-security-audit",
+					"$resource.spec/artifacts/score-lint-error",
+					"$resource.spec/artifacts/score-accuracy",
+					"$resource.spec/artifacts/score-lang-reuse",
 				},
-			})
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			ctx := context.Background()
+
+			SetUpProject(ctx, t, test.project)
+
+			client, err := connection.NewClient(ctx)
 			if err != nil {
-				t.Fatalf("Failed to create project %s: %s", test.project, err.Error())
+				t.Fatalf("Setup: Failed to create client: %s", err)
 			}
 
 			cmd := Command()
@@ -98,16 +274,16 @@ func TestManifestArtifactUpload(t *testing.T) {
 			}
 
 			req := &rpc.GetArtifactContentsRequest{
-				Name: "projects/" + test.project + "/locations/global/artifacts/test-manifest",
+				Name: "projects/" + test.project + "/locations/global/artifacts/test-scorecard-definition",
 			}
 
-			manifest := &rpc.Manifest{}
+			definition := &rpc.ScoreCardDefinition{}
 			body, err := client.GetArtifactContents(ctx, req)
 			if err != nil {
 				t.Fatalf("GetArtifactContents() returned error: %s", err)
 			}
 			contents := body.GetData()
-			err = proto.Unmarshal(contents, manifest)
+			err = proto.Unmarshal(contents, definition)
 			if err != nil {
 				t.Fatalf("proto.Unmarshal() returned error: %s", err)
 			}
@@ -117,7 +293,192 @@ func TestManifestArtifactUpload(t *testing.T) {
 				protocmp.Transform(),
 			}
 
-			if diff := cmp.Diff(test.want, manifest, opts); diff != "" {
+			if diff := cmp.Diff(test.want, definition, opts); diff != "" {
+				t.Errorf("GetArtifactContents returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestScoreArtifactUpload(t *testing.T) {
+	tests := []struct {
+		desc     string
+		project  string
+		filePath string
+		want     *rpc.Score
+	}{
+		{
+			desc:     "simple score artifact upload",
+			project:  "upload-score-artifact-demo",
+			filePath: filepath.Join("testdata", "score.yaml"),
+			want: &rpc.Score{
+				Id:             "test-score",
+				Kind:           "Score",
+				DefinitionName: "projects/upload-score-artifact-demo/locations/global/artifacts/test-score-definition",
+				Severity:       rpc.Severity_OK,
+				Value: &rpc.Score_IntegerValue{
+					IntegerValue: &rpc.IntegerValue{
+						Value:    50,
+						MinValue: 0,
+						MaxValue: 100,
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			ctx := context.Background()
+
+			SetUpProject(ctx, t, test.project)
+
+			client, err := connection.NewClient(ctx)
+			if err != nil {
+				t.Fatalf("Setup: Failed to create client: %s", err)
+			}
+
+			cmd := Command()
+			args := []string{"artifact", test.filePath, "--parent", fmt.Sprintf("projects/%s/locations/global", test.project)}
+			cmd.SetArgs(args)
+			if err = cmd.Execute(); err != nil {
+				t.Fatalf("Execute() with args %v returned error: %s", args, err)
+			}
+
+			req := &rpc.GetArtifactContentsRequest{
+				Name: "projects/" + test.project + "/locations/global/artifacts/test-score",
+			}
+
+			definition := &rpc.Score{}
+			body, err := client.GetArtifactContents(ctx, req)
+			if err != nil {
+				t.Fatalf("GetArtifactContents() returned error: %s", err)
+			}
+			contents := body.GetData()
+			err = proto.Unmarshal(contents, definition)
+			if err != nil {
+				t.Fatalf("proto.Unmarshal() returned error: %s", err)
+			}
+
+			// Verify the manifest definition is correct
+			opts := cmp.Options{
+				protocmp.Transform(),
+			}
+
+			if diff := cmp.Diff(test.want, definition, opts); diff != "" {
+				t.Errorf("GetArtifactContents returned unexpected diff (-want +got):\n%s", diff)
+			}
+		})
+	}
+}
+
+func TestScoreCardArtifactUpload(t *testing.T) {
+	tests := []struct {
+		desc     string
+		project  string
+		filePath string
+		want     *rpc.ScoreCard
+	}{
+		{
+			desc:     "simple scorecard artifact upload",
+			project:  "upload-scorecard-artifact-demo",
+			filePath: filepath.Join("testdata", "scorecard.yaml"),
+			want: &rpc.ScoreCard{
+				Id:             "test-scorecard",
+				Kind:           "ScoreCard",
+				DefinitionName: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/test-scorecard-definition",
+				Scores: []*rpc.Score{
+					{
+						Id:             "score-security-audit",
+						Kind:           "Score",
+						DefinitionName: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-security-audit",
+						Severity:       rpc.Severity_OK,
+						Value: &rpc.Score_BooleanValue{
+							BooleanValue: &rpc.BooleanValue{
+								Value:        true,
+								DisplayValue: "Approved",
+							},
+						},
+					},
+					{
+						Id:             "score-lint-error",
+						Kind:           "Score",
+						DefinitionName: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-lint-error",
+						Severity:       rpc.Severity_OK,
+						Value: &rpc.Score_IntegerValue{
+							IntegerValue: &rpc.IntegerValue{
+								Value:    50,
+								MinValue: 0,
+								MaxValue: 100,
+							},
+						},
+					},
+					{
+						Id:             "score-accuracy",
+						Kind:           "Score",
+						DefinitionName: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-accuracy",
+						Severity:       rpc.Severity_ALERT,
+						Value: &rpc.Score_PercentValue{
+							PercentValue: &rpc.PercentValue{
+								Value: 50,
+							},
+						},
+					},
+					{
+						Id:             "score-lang-reuse",
+						Kind:           "Score",
+						DefinitionName: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-lang-reuse",
+						Severity:       rpc.Severity_OK,
+						Value: &rpc.Score_PercentValue{
+							PercentValue: &rpc.PercentValue{
+								Value: 70,
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			ctx := context.Background()
+
+			SetUpProject(ctx, t, test.project)
+
+			client, err := connection.NewClient(ctx)
+			if err != nil {
+				t.Fatalf("Setup: Failed to create client: %s", err)
+			}
+
+			cmd := Command()
+			args := []string{"artifact", test.filePath, "--parent", fmt.Sprintf("projects/%s/locations/global", test.project)}
+			cmd.SetArgs(args)
+			if err = cmd.Execute(); err != nil {
+				t.Fatalf("Execute() with args %v returned error: %s", args, err)
+			}
+
+			req := &rpc.GetArtifactContentsRequest{
+				Name: "projects/" + test.project + "/locations/global/artifacts/test-scorecard",
+			}
+
+			definition := &rpc.ScoreCard{}
+			body, err := client.GetArtifactContents(ctx, req)
+			if err != nil {
+				t.Fatalf("GetArtifactContents() returned error: %s", err)
+			}
+			contents := body.GetData()
+			err = proto.Unmarshal(contents, definition)
+			if err != nil {
+				t.Fatalf("proto.Unmarshal() returned error: %s", err)
+			}
+
+			// Verify the manifest definition is correct
+			opts := cmp.Options{
+				protocmp.Transform(),
+			}
+
+			if diff := cmp.Diff(test.want, definition, opts); diff != "" {
 				t.Errorf("GetArtifactContents returned unexpected diff (-want +got):\n%s", diff)
 			}
 		})
diff --git a/cmd/registry/cmd/upload/testdata/score-definition.yaml b/cmd/registry/cmd/upload/testdata/score-definition.yaml
new file mode 100644
index 0000000..c8fb23e
--- /dev/null
+++ b/cmd/registry/cmd/upload/testdata/score-definition.yaml
@@ -0,0 +1,34 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+id: "test-score-definition"
+kind: "ScoreDefinition"
+target_resource:
+  pattern: "apis/-/versions/-/specs/-"
+score_formula:
+  pattern:
+    pattern: "$resource.spec/artifacts/conformance-report"
+  score_expression: "size(conformance.GuidelineReportGroups[2].RuleReportGroups[1])"
+integer: 
+  min_value: 0
+  max_value: 100
+  thresholds:
+  - severity: ALERT
+    range:
+      min: 60
+      max: 100
+  - severity: OK
+    range:
+      min: 0
+      max: 59
diff --git a/cmd/registry/cmd/upload/testdata/score.yaml b/cmd/registry/cmd/upload/testdata/score.yaml
new file mode 100644
index 0000000..eebde98
--- /dev/null
+++ b/cmd/registry/cmd/upload/testdata/score.yaml
@@ -0,0 +1,22 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+id: "test-score"
+kind: "Score"
+definition_name: "projects/upload-score-artifact-demo/locations/global/artifacts/test-score-definition"
+severity: OK
+integer_value:
+  value: 50
+  min_value: 0
+  max_value: 100
diff --git a/cmd/registry/cmd/upload/testdata/scorecard-definition.yaml b/cmd/registry/cmd/upload/testdata/scorecard-definition.yaml
new file mode 100644
index 0000000..a342243
--- /dev/null
+++ b/cmd/registry/cmd/upload/testdata/scorecard-definition.yaml
@@ -0,0 +1,23 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+id: "test-scorecard-definition"
+kind: "ScoreCardDefinition"
+target_resource:
+  pattern: "apis/-/versions/-/specs/-"
+score_patterns:
+- "$resource.spec/artifacts/score-security-audit"
+- "$resource.spec/artifacts/score-lint-error"
+- "$resource.spec/artifacts/score-accuracy"
+- "$resource.spec/artifacts/score-lang-reuse"
diff --git a/cmd/registry/cmd/upload/testdata/scorecard.yaml b/cmd/registry/cmd/upload/testdata/scorecard.yaml
new file mode 100644
index 0000000..7a18e36
--- /dev/null
+++ b/cmd/registry/cmd/upload/testdata/scorecard.yaml
@@ -0,0 +1,45 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+id: "test-scorecard"
+kind: "ScoreCard"
+definition_name: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/test-scorecard-definition"
+scores:
+- id: "score-security-audit"
+  kind: "Score"
+  definition_name: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-security-audit"
+  severity: OK
+  boolean_value:
+    value: true
+    display_value: "Approved"
+- id: "score-lint-error"
+  kind: "Score"
+  definition_name: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-lint-error"
+  severity: OK
+  integer_value:
+    value: 50
+    min_value: 0
+    max_value: 100
+- id: "score-accuracy"
+  kind: "Score"
+  definition_name: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-accuracy"
+  severity: ALERT
+  percent_value:
+    value: 50
+- id: "score-lang-reuse"
+  kind: "Score"
+  definition_name: "projects/upload-scorecard-artifact-demo/locations/global/artifacts/definition-lang-reuse"
+  severity: OK
+  percent_value:
+    value: 70
\ No newline at end of file
diff --git a/cmd/registry/controller/parser_test.go b/cmd/registry/controller/parser_test.go
index 0df6934..7f1524b 100644
--- a/cmd/registry/controller/parser_test.go
+++ b/cmd/registry/controller/parser_test.go
@@ -19,300 +19,8 @@ import (
 	"testing"
 
 	"github.com/apigee/registry/rpc"
-	"github.com/apigee/registry/server/registry/names"
 )
 
-func generateSpec(t *testing.T, specName string) names.Spec {
-	t.Helper()
-	spec, err := names.ParseSpec(specName)
-	if err != nil {
-		t.Fatalf("Failed generateSpec(%s): %s", specName, err.Error())
-	}
-	return spec
-}
-
-func generateVersion(t *testing.T, versionName string) names.Version {
-	t.Helper()
-	version, err := names.ParseVersion(versionName)
-	if err != nil {
-		t.Fatalf("Failed generateSpec(%s): %s", versionName, err.Error())
-	}
-	return version
-}
-
-func generateArtifact(t *testing.T, artifactName string) names.Artifact {
-	t.Helper()
-	artifact, err := names.ParseArtifact(artifactName)
-	if err != nil {
-		t.Fatalf("Failed generateSpec(%s): %s", artifactName, err.Error())
-	}
-	return artifact
-}
-
-func TestExtendDependencyPattern(t *testing.T) {
-	tests := []struct {
-		desc              string
-		resourcePattern   string
-		dependencyPattern string
-		want              string
-	}{
-		{
-			desc:              "artifact reference",
-			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/lint-gnostic",
-			dependencyPattern: "$resource.artifact",
-			want:              "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/lint-gnostic",
-		},
-		{
-			desc:              "spec reference",
-			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-",
-			dependencyPattern: "$resource.spec",
-			want:              "projects/demo/locations/global/apis/-/versions/-/specs/-",
-		},
-		{
-			desc:              "version reference",
-			resourcePattern:   "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/-",
-			dependencyPattern: "$resource.version/artifacts/lintstats",
-			want:              "projects/demo/locations/global/apis/petstore/versions/1.0.0/artifacts/lintstats",
-		},
-		{
-			desc:              "api reference",
-			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
-			dependencyPattern: "$resource.version/artifacts/lintstats",
-			want:              "projects/demo/locations/global/apis/-/versions/-/artifacts/lintstats",
-		},
-		{
-			desc:              "no reference",
-			resourcePattern:   "projects/demo/locations/global/apis/-/artifacts/lintstats",
-			dependencyPattern: "apis/-/versions/-",
-			want:              "projects/demo/locations/global/apis/-/versions/-",
-		},
-	}
-
-	const projectID = "demo"
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := extendDependencyPattern(test.resourcePattern, test.dependencyPattern, projectID)
-			if err != nil {
-				t.Errorf("extendDependencyPattern returned unexpected error: %s", err)
-			}
-			if got != test.want {
-				t.Errorf("extendDependencyPattern returned unexpected value want: %q got:%q", test.want, got)
-			}
-		})
-	}
-}
-
-func TestExtendDependencyPatternError(t *testing.T) {
-	tests := []struct {
-		desc              string
-		resourcePattern   string
-		dependencyPattern string
-	}{
-		{
-			desc:              "non-existent reference",
-			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
-			dependencyPattern: "$resource.artifact",
-		},
-		{
-			desc:              "incorrect reference keyword",
-			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
-			dependencyPattern: "$resource.aip",
-		},
-		{
-			desc:              "incorrect resourcePattern",
-			resourcePattern:   "projects/demo/locations/global/-/versions/-/specs/-",
-			dependencyPattern: "$resource.api/artifacts/lintstats",
-		},
-	}
-
-	const projectID = "demo"
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := extendDependencyPattern(test.resourcePattern, test.dependencyPattern, projectID)
-			if err == nil {
-				t.Errorf("expected extendDependencyPattern to return error, got: %q", got)
-			}
-		})
-	}
-}
-
-func TestResourceNameFromParent(t *testing.T) {
-	tests := []struct {
-		desc            string
-		resourcePattern string
-		parent          string
-		want            resourceName
-	}{
-		{
-			desc:            "version pattern",
-			resourcePattern: "projects/demo/locations/global/apis/-/versions/1.0.0",
-			parent:          "projects/demo/locations/global/apis/petstore",
-			want: versionName{
-				version: generateVersion(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0"),
-			},
-		},
-		{
-			desc:            "spec pattern",
-			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/openapi.yaml",
-			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0",
-			want: specName{
-				spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"),
-			},
-		},
-		{
-			desc:            "artifact pattern",
-			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/complexity",
-			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
-			want: artifactName{
-				artifact: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity"),
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := resourceNameFromParent(test.resourcePattern, test.parent)
-			if err != nil {
-				t.Errorf("resourceNameFromEntityKey returned unexpected error: %s", err)
-			}
-			if got != test.want {
-				t.Errorf("resourceNameFromEntityKey returned unexpected value want: %q got:%q", test.want, got)
-			}
-		})
-	}
-
-}
-
-func TestResourceNameFromParentError(t *testing.T) {
-	tests := []struct {
-		desc            string
-		resourcePattern string
-		parent          string
-	}{
-		{
-			desc:            "incorrect keywords",
-			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/apispecs/-",
-			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
-		},
-		{
-			desc:            "incorrect pattern",
-			resourcePattern: "projects/demo/locations/global/apis/-/specs/-",
-			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := resourceNameFromParent(test.resourcePattern, test.parent)
-			if err == nil {
-				t.Errorf("expected resourceNameFromEntityKey to return error, got: %q", got)
-			}
-		})
-	}
-
-}
-
-func TestGetEntityKey(t *testing.T) {
-	tests := []struct {
-		desc     string
-		pattern  string
-		resource resourceInstance
-		want     string
-	}{
-		{
-			desc:    "api group",
-			pattern: "$resource.api/versions/-/specs/-",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-			want: "projects/demo/locations/global/apis/petstore",
-		},
-		{
-			desc:    "version group",
-			pattern: "$resource.version/specs/-",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-			want: "projects/demo/locations/global/apis/petstore/versions/1.0.0",
-		},
-		{
-			desc:    "spec group",
-			pattern: "$resource.spec",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-			want: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
-		},
-		{
-			desc:    "artifact group",
-			pattern: "$resource.artifact",
-			resource: artifactResource{
-				artifactName: artifactName{artifact: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")},
-			},
-			want: "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic",
-		},
-		{
-			desc:    "no group",
-			pattern: "apis/-/versions/-/specs/-",
-			resource: artifactResource{
-				artifactName: artifactName{artifact: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")},
-			},
-			want: "default",
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := getEntityKey(test.pattern, test.resource.ResourceName())
-			if err != nil {
-				t.Errorf("getEntityKey returned unexpected error: %s", err)
-			}
-			if got != test.want {
-				t.Errorf("getEntityKey returned unexpected value want: %q got:%q", test.want, got)
-			}
-		})
-	}
-}
-
-func TestGetEntityKeyError(t *testing.T) {
-	tests := []struct {
-		desc     string
-		pattern  string
-		resource resourceInstance
-	}{
-		{
-			desc:    "typo",
-			pattern: "$resource.apis/versions/-/specs/-",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-		},
-		{
-			desc:    "incorrect reference",
-			pattern: "$resource.name/versions/-/specs/-",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-		},
-		{
-			desc:    "incorrect resourceKW",
-			pattern: "$resources.api/versions/-/specs/-",
-			resource: specResource{
-				specName: specName{spec: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.desc, func(t *testing.T) {
-			got, err := getEntityKey(test.pattern, test.resource.ResourceName())
-			if err == nil {
-				t.Errorf("expected getEntityKey to return error, got: %q", got)
-			}
-		})
-	}
-}
-
 func TestGenerateCommand(t *testing.T) {
 	tests := []struct {
 		desc         string
diff --git a/cmd/registry/patterns/parser_test.go b/cmd/registry/patterns/parser_test.go
new file mode 100644
index 0000000..e8dc781
--- /dev/null
+++ b/cmd/registry/patterns/parser_test.go
@@ -0,0 +1,285 @@
+package patterns
+
+import (
+	"testing"
+
+	"github.com/apigee/registry/server/registry/names"
+)
+
+func generateSpec(t *testing.T, specName string) names.Spec {
+	t.Helper()
+	spec, err := names.ParseSpec(specName)
+	if err != nil {
+		t.Fatalf("Failed generateSpec(%s): %s", specName, err.Error())
+	}
+	return spec
+}
+
+func generateVersion(t *testing.T, versionName string) names.Version {
+	t.Helper()
+	version, err := names.ParseVersion(versionName)
+	if err != nil {
+		t.Fatalf("Failed generateVersion(%s): %s", versionName, err.Error())
+	}
+	return version
+}
+
+func generateArtifact(t *testing.T, artifactName string) names.Artifact {
+	t.Helper()
+	artifact, err := names.ParseArtifact(artifactName)
+	if err != nil {
+		t.Fatalf("Failed generateArtifact(%s): %s", artifactName, err.Error())
+	}
+	return artifact
+}
+
+func TestSubstitueReferenceEntity(t *testing.T) {
+	tests := []struct {
+		desc              string
+		resourcePattern   string
+		dependencyPattern string
+		want              string
+	}{
+		{
+			desc:              "artifact reference",
+			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/lint-gnostic",
+			dependencyPattern: "$resource.artifact",
+			want:              "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/lint-gnostic",
+		},
+		{
+			desc:              "spec reference",
+			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/-",
+			dependencyPattern: "$resource.spec",
+			want:              "projects/demo/locations/global/apis/-/versions/-/specs/-",
+		},
+		{
+			desc:              "version reference",
+			resourcePattern:   "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/-",
+			dependencyPattern: "$resource.version/artifacts/lintstats",
+			want:              "projects/demo/locations/global/apis/petstore/versions/1.0.0/artifacts/lintstats",
+		},
+		{
+			desc:              "api reference",
+			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
+			dependencyPattern: "$resource.version/artifacts/lintstats",
+			want:              "projects/demo/locations/global/apis/-/versions/-/artifacts/lintstats",
+		},
+		{
+			desc:              "no reference",
+			resourcePattern:   "projects/demo/locations/global/apis/-/artifacts/lintstats",
+			dependencyPattern: "apis/-/versions/-",
+			want:              "projects/demo/locations/global/apis/-/versions/-",
+		},
+	}
+
+	const projectID = "demo"
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			resourceName, err := ParseResourcePattern(test.resourcePattern)
+			if err != nil {
+				t.Fatalf("Error in parsing, %s", err)
+			}
+			got, err := SubstituteReferenceEntity(test.dependencyPattern, resourceName, projectID)
+			if err != nil {
+				t.Errorf("SubstituteReferenceEntity returned unexpected error: %s", err)
+			}
+			if got != test.want {
+				t.Errorf("SubstituteReferenceEntity returned unexpected value want: %q got:%q", test.want, got)
+			}
+		})
+	}
+}
+
+func TestSubstitueReferenceEntityError(t *testing.T) {
+	tests := []struct {
+		desc              string
+		resourcePattern   string
+		dependencyPattern string
+	}{
+		{
+			desc:              "non-existent reference",
+			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
+			dependencyPattern: "$resource.artifact",
+		},
+		{
+			desc:              "incorrect reference keyword",
+			resourcePattern:   "projects/demo/locations/global/apis/-/versions/-/specs/-",
+			dependencyPattern: "$resource.aip",
+		},
+	}
+
+	const projectID = "demo"
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			resourceName, err := ParseResourcePattern(test.resourcePattern)
+			if err != nil {
+				t.Fatalf("Error in parsing, %s", err)
+			}
+			got, err := SubstituteReferenceEntity(test.dependencyPattern, resourceName, projectID)
+			if err == nil {
+				t.Errorf("expected SubstituteReferenceEntity to return error, got: %q", got)
+			}
+		})
+	}
+}
+
+func TestFullResourceNameFromParent(t *testing.T) {
+	tests := []struct {
+		desc            string
+		resourcePattern string
+		parent          string
+		want            ResourceName
+	}{
+		{
+			desc:            "version pattern",
+			resourcePattern: "projects/demo/locations/global/apis/-/versions/1.0.0",
+			parent:          "projects/demo/locations/global/apis/petstore",
+			want: VersionName{
+				Name: generateVersion(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0"),
+			},
+		},
+		{
+			desc:            "spec pattern",
+			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/openapi.yaml",
+			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0",
+			want: SpecName{
+				Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml"),
+			},
+		},
+		{
+			desc:            "artifact pattern",
+			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/specs/-/artifacts/complexity",
+			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
+			want: ArtifactName{
+				Name: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/complexity"),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			got, err := FullResourceNameFromParent(test.resourcePattern, test.parent)
+			if err != nil {
+				t.Errorf("FullResourceNameFromParent returned unexpected error: %s", err)
+			}
+			if got != test.want {
+				t.Errorf("FullResourceNameFromParent returned unexpected value want: %q got:%q", test.want, got)
+			}
+		})
+	}
+
+}
+
+func TestFullResourceNameFromParentError(t *testing.T) {
+	tests := []struct {
+		desc            string
+		resourcePattern string
+		parent          string
+	}{
+		{
+			desc:            "incorrect keywords",
+			resourcePattern: "projects/demo/locations/global/apis/-/versions/-/apispecs/-",
+			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
+		},
+		{
+			desc:            "incorrect pattern",
+			resourcePattern: "projects/demo/locations/global/apis/-/specs/-",
+			parent:          "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			got, err := FullResourceNameFromParent(test.resourcePattern, test.parent)
+			if err == nil {
+				t.Errorf("expected FullResourceNameFromParent to return error, got: %q", got)
+			}
+		})
+	}
+
+}
+
+func TestGetReferenceEntityValue(t *testing.T) {
+	tests := []struct {
+		desc            string
+		resourcePattern string
+		referred        ResourceName
+		want            string
+	}{
+		{
+			desc:            "api group",
+			resourcePattern: "$resource.api/versions/-/specs/-",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+			want:            "projects/demo/locations/global/apis/petstore",
+		},
+		{
+			desc:            "version group",
+			resourcePattern: "$resource.version/specs/-",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+			want:            "projects/demo/locations/global/apis/petstore/versions/1.0.0",
+		},
+		{
+			desc:            "spec group",
+			resourcePattern: "$resource.spec",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+			want:            "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml",
+		},
+		{
+			desc:            "artifact group",
+			resourcePattern: "$resource.artifact",
+			referred:        ArtifactName{Name: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")},
+			want:            "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic",
+		},
+		{
+			desc:            "no group",
+			resourcePattern: "apis/-/versions/-/specs/-",
+			referred:        ArtifactName{Name: generateArtifact(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml/artifacts/lint-gnostic")},
+			want:            "default",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			got, err := GetReferenceEntityValue(test.resourcePattern, test.referred)
+			if err != nil {
+				t.Errorf("GetReferenceEntityValue returned unexpected error: %s", err)
+			}
+			if got != test.want {
+				t.Errorf("GetReferenceEntityValue returned unexpected value want: %q got:%q", test.want, got)
+			}
+		})
+	}
+}
+
+func TestGetReferenceEntityValueError(t *testing.T) {
+	tests := []struct {
+		desc            string
+		resourcePattern string
+		referred        ResourceName
+	}{
+		{
+			desc:            "typo",
+			resourcePattern: "$resource.apis/versions/-/specs/-",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+		},
+		{
+			desc:            "incorrect reference",
+			resourcePattern: "$resource.name/versions/-/specs/-",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+		},
+		{
+			desc:            "incorrect resourceKW",
+			resourcePattern: "$resources.api/versions/-/specs/-",
+			referred:        SpecName{Name: generateSpec(t, "projects/demo/locations/global/apis/petstore/versions/1.0.0/specs/openapi.yaml")},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.desc, func(t *testing.T) {
+			got, err := GetReferenceEntityValue(test.resourcePattern, test.referred)
+			if err == nil {
+				t.Errorf("expected GetReferenceEntityValue to return error, got: %q", got)
+			}
+		})
+	}
+}