Repository: akuity/kargo
Pull request #1351 (2024-01-03 06:12:12)

refactor(cli): flag cleanup
This PR fixes two things I noticed today:

1. There's a lot of unnecessary complexity in how we're setting up widely used flags for various CLI sub-commands. A little bit of simplification makes this much easier to follow.

1. The login command was doing flags very differently from how all other commands were, so I fixed it to be more consistent.

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *docs-kargo-akuity-io* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 155f91634da9447f6dd6018f3785e56e01fabf72 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/docs-kargo-akuity-io/deploys/6594fabf7825eb00098bc276 |
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity) Report
Attention: `20 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`c343c41`)](https://app.codecov.io/gh/akuity/kargo/commit/c343c41c0bdae68db5f366f7dc5fe04cb3c6cdb5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity) 45.56% compared to head [(`155f916`)](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity) 45.61%.

| [Files](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity) | Patch % | Lines |
|---|---|---|
| [internal/cli/cmd/login/login.go](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity#diff-aW50ZXJuYWwvY2xpL2NtZC9sb2dpbi9sb2dpbi5nbw==) | 0.00% | [20 Missing :warning: ](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1351      +/-   ##
==========================================
+ Coverage   45.56%   45.61%   +0.05%     
==========================================
  Files         135      135              
  Lines       11657    11644      -13     
==========================================
  Hits         5311     5311              
+ Misses       6155     6142      -13     
  Partials      191      191              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/akuity/kargo/pull/1351?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=akuity).


Patch:
diff --git a/Dockerfile b/Dockerfile
index 83cae15b..2107bbfe 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -68,7 +68,7 @@ RUN GRPC_HEALTH_PROBE_VERSION=v0.4.15 && \
 # - supports development
 # - not used for official image builds
 ####################################################################################################
-FROM ghcr.io/akuity/kargo-render:v0.1.0-rc.33 as back-end-dev
+FROM ghcr.io/akuity/kargo-render:v0.1.0-rc.34 as back-end-dev
 
 USER root
 
@@ -103,7 +103,7 @@ CMD ["pnpm", "dev"]
 # - the official image we publish
 # - purposefully last so that it is the default target when building
 ####################################################################################################
-FROM ghcr.io/akuity/kargo-render:v0.1.0-rc.33 as final
+FROM ghcr.io/akuity/kargo-render:v0.1.0-rc.34 as final
 
 USER root
 
diff --git a/Tiltfile b/Tiltfile
index 0e1caaec..a32c4714 100644
--- a/Tiltfile
+++ b/Tiltfile
@@ -120,6 +120,19 @@ k8s_resource(
   resource_deps=['back-end-compile']
 )
 
+k8s_resource(
+  workload = 'kargo-management-controller',
+  new_name = 'management-controller',
+  labels = ['kargo'],
+  objects = [
+    'kargo-management-controller:clusterrole',
+    'kargo-management-controller:clusterrolebinding',
+    'kargo-management-controller:configmap',
+    'kargo-management-controller:serviceaccount'
+  ],
+  resource_deps=['back-end-compile']
+)
+
 k8s_resource(
   workload = 'kargo-ui',
   new_name = 'ui',
@@ -152,9 +165,10 @@ k8s_resource(
   new_name = 'crds',
   objects = [
     'freights.kargo.akuity.io:customresourcedefinition',
-    'stages.kargo.akuity.io:customresourcedefinition',
+    'projects.kargo.akuity.io:customresourcedefinition',
     'promotionpolicies.kargo.akuity.io:customresourcedefinition',
     'promotions.kargo.akuity.io:customresourcedefinition',
+    'stages.kargo.akuity.io:customresourcedefinition',
     'warehouses.kargo.akuity.io:customresourcedefinition'
   ],
   labels = ['kargo']
diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go
index 1497c2fb..e7d2985d 100644
--- a/api/v1alpha1/groupversion_info.go
+++ b/api/v1alpha1/groupversion_info.go
@@ -31,6 +31,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
 		&FreightList{},
 		&Stage{},
 		&StageList{},
+		&Project{},
+		&ProjectList{},
 		&Promotion{},
 		&PromotionList{},
 		&PromotionPolicy{},
diff --git a/api/v1alpha1/project_types.go b/api/v1alpha1/project_types.go
new file mode 100644
index 00000000..0b8d0cd8
--- /dev/null
+++ b/api/v1alpha1/project_types.go
@@ -0,0 +1,46 @@
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+//+kubebuilder:object:root=true
+//+kubebuilder:resource:scope=Cluster
+//+kubebuilder:subresource:status
+//+kubebuilder:printcolumn:name=Age,type=date,JSONPath=`.metadata.creationTimestamp`
+
+// Project is a resource type that reconciles to a specially labeled namespace
+// and other TODO: TBD project-level resources.
+type Project struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+	// Spec describes a Project.
+	//
+	//+kubebuilder:validation:Required
+	Spec *ProjectSpec `json:"spec"`
+	// Status describes the Project's current status.
+	Status ProjectStatus `json:"status,omitempty"`
+}
+
+func (p *Project) GetStatus() *ProjectStatus {
+	return &p.Status
+}
+
+// ProjectSpec describes a Project.
+type ProjectSpec struct {
+	// TODO: Figure out the attributes of a ProjectSpec.
+}
+
+// ProjectStatus describes a Project's current status.
+type ProjectStatus struct {
+	// TODO: Figure out the attributes of a ProjectStatus.
+}
+
+//+kubebuilder:object:root=true
+
+// ProjectList is a list of Project resources.
+type ProjectList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []Project `json:"items"`
+}
diff --git a/api/v1alpha1/stage_types.go b/api/v1alpha1/stage_types.go
index 7a8c6a9c..3c1a52cb 100644
--- a/api/v1alpha1/stage_types.go
+++ b/api/v1alpha1/stage_types.go
@@ -159,6 +159,10 @@ type GitRepoUpdate struct {
 	//+kubebuilder:validation:MinLength=1
 	//+kubebuilder:validation:Pattern=`^https://(\w+([\.-]\w+)*@)?\w+([\.-]\w+)*(:[\d]+)?(/.*)?$`
 	RepoURL string `json:"repoURL"`
+	// InsecureSkipTLSVerify specifies whether certificate verification errors
+	// should be ignored when connecting to the repository. This should be enabled
+	// only with great caution.
+	InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
 	// ReadBranch specifies a particular branch of the repository from which to
 	// locate contents that will be written to the branch specified by the
 	// WriteBranch field. This field is optional. When not specified, the
@@ -450,10 +454,10 @@ type StageStatus struct {
 	Phase StagePhase `json:"phase,omitempty"`
 	// CurrentFreight is a simplified representation of the Stage's current
 	// Freight describing what is currently deployed to the Stage.
-	CurrentFreight *SimpleFreight `json:"currentFreight,omitempty"`
+	CurrentFreight *FreightReference `json:"currentFreight,omitempty"`
 	// History is a stack of recent Freight. By default, the last ten Freight are
 	// stored.
-	History SimpleFreightStack `json:"history,omitempty"`
+	History FreightReferenceStack `json:"history,omitempty"`
 	// Health is the Stage's last observed health.
 	Health *Health `json:"health,omitempty"`
 	// Error describes any errors that are preventing the Stage controller
@@ -466,9 +470,9 @@ type StageStatus struct {
 	CurrentPromotion *PromotionInfo `json:"currentPromotion,omitempty"`
 }
 
-// SimpleFreight is a simplified representation of a piece of Freight -- not a
-// root resource type.
-type SimpleFreight struct {
+// FreightReference is a simplified representation of a piece of Freight -- not
+// a root resource type.
+type FreightReference struct {
 	// ID is system-assigned value that is derived deterministically from the
 	// contents of the Freight. i.e. Two pieces of Freight can be compared for
 	// equality by comparing their IDs.
@@ -484,37 +488,37 @@ type SimpleFreight struct {
 	VerificationInfo *VerificationInfo `json:"verificationResult,omitempty"`
 }
 
-type SimpleFreightStack []SimpleFreight
+type FreightReferenceStack []FreightReference
 
-// Empty returns a bool indicating whether or not the SimpleFreightStack is
+// Empty returns a bool indicating whether or not the FreightReferenceStack is
 // empty. nil counts as empty.
-func (s SimpleFreightStack) Empty() bool {
-	return len(s) == 0
+func (f FreightReferenceStack) Empty() bool {
+	return len(f) == 0
 }
 
-// Pop removes and returns the leading element from a SimpleFreightStack. If the
-// SimpleFreightStack is empty, the SimpleFreightStack is not modified and a
-// empty SimpleFreight is returned instead. A boolean is also returned
-// indicating whether the returned SimpleFreight came from the top of the stack
-// (true) or is a zero value for that type (false).
-func (s *SimpleFreightStack) Pop() (SimpleFreight, bool) {
-	item, ok := s.Top()
+// Pop removes and returns the leading element from a FreightReferenceStack. If
+// the FreightReferenceStack is empty, the FreightReferenceStack is not modified
+// and a empty FreightReference is returned instead. A boolean is also returned
+// indicating whether the returned FreightReference came from the top of the
+// stack (true) or is a zero value for that type (false).
+func (f *FreightReferenceStack) Pop() (FreightReference, bool) {
+	item, ok := f.Top()
 	if ok {
-		*s = (*s)[1:]
+		*f = (*f)[1:]
 	}
 	return item, ok
 }
 
-// Top returns the leading element from a SimpleFreightStack without modifying
-// the SimpleFreightStack. If the SimpleFreightStack is empty, an empty
-// SimpleFreight is returned instead. A boolean is also returned indicating
-// whether the returned SimpleFreight came from the top of the stack (true) or
-// is a zero value for that type (false).
-func (s SimpleFreightStack) Top() (SimpleFreight, bool) {
-	if s.Empty() {
-		return SimpleFreight{}, false
+// Top returns the leading element from a FreightReferenceStack without
+// modifying the FreightReferenceStack. If the FreightReferenceStack is empty,
+// an empty FreightReference is returned instead. A boolean is also returned
+// indicating whether the returned FreightReference came from the top of the
+// stack (true) or is a zero value for that type (false).
+func (f FreightReferenceStack) Top() (FreightReference, bool) {
+	if f.Empty() {
+		return FreightReference{}, false
 	}
-	item := *s[0].DeepCopy()
+	item := *f[0].DeepCopy()
 	return item, true
 }
 
@@ -524,11 +528,11 @@ func (s SimpleFreightStack) Top() (SimpleFreight, bool) {
 // the element at the top of the stack. If resulting modification grow the depth
 // of the stack beyond 10 elements, the stack is truncated at the bottom. i.e.
 // Modified to contain only the top 10 elements.
-func (s *SimpleFreightStack) Push(freight ...SimpleFreight) {
-	*s = append(freight, *s...)
+func (f *FreightReferenceStack) Push(freight ...FreightReference) {
+	*f = append(freight, *f...)
 	const max = 10
-	if len(*s) > max {
-		*s = (*s)[:max]
+	if len(*f) > max {
+		*f = (*f)[:max]
 	}
 }
 
@@ -619,7 +623,7 @@ type PromotionInfo struct {
 	// Name is the name of the Promotion
 	Name string `json:"name"`
 	// Freight is the freight being promoted
-	Freight SimpleFreight `json:"freight"`
+	Freight FreightReference `json:"freight"`
 }
 
 // Verification describes how to verify that a Promotion has been successful
diff --git a/api/v1alpha1/types.proto b/api/v1alpha1/types.proto
index 53f4b886..02e4bd8c 100644
--- a/api/v1alpha1/types.proto
+++ b/api/v1alpha1/types.proto
@@ -163,7 +163,7 @@ message Promotion {
 
 message PromotionInfo {
   string name = 1 [json_name = "name"];
-  SimpleFreight freight = 2 [json_name = "freight"];
+  FreightReference freight = 2 [json_name = "freight"];
 }
 
 message PromotionList {
@@ -247,7 +247,7 @@ message VerifiedStage {
 message ApprovedStage {
 }
 
-message SimpleFreight {
+message FreightReference {
   string id = 1 [json_name = "id"];
   optional google.protobuf.Timestamp first_seen = 2 [json_name = "firstSeen"];
   repeated GitCommit commits = 4 [json_name = "commits"];
@@ -257,8 +257,8 @@ message SimpleFreight {
 }
 
 message StageStatus {
-  optional SimpleFreight current_freight = 2 [json_name = "currentFreight"];
-  repeated SimpleFreight history = 3 [json_name = "history"];
+  optional FreightReference current_freight = 2 [json_name = "currentFreight"];
+  repeated FreightReference history = 3 [json_name = "history"];
   string error = 4 [json_name = "error"];
   optional Health health = 5 [json_name = "health"];
   optional PromotionInfo current_promotion = 6 [json_name = "currentPromotion"];
diff --git a/api/v1alpha1/warehouse_types.go b/api/v1alpha1/warehouse_types.go
index 71a6461f..38d54fef 100644
--- a/api/v1alpha1/warehouse_types.go
+++ b/api/v1alpha1/warehouse_types.go
@@ -66,6 +66,10 @@ type GitSubscription struct {
 	//+kubebuilder:validation:MinLength=1
 	//+kubebuilder:validation:Pattern=`^\w+([-/]\w+)*$`
 	Branch string `json:"branch,omitempty"`
+	// InsecureSkipTLSVerify specifies whether certificate verification errors
+	// should be ignored when connecting to the repository. This should be enabled
+	// only with great caution.
+	InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify,omitempty"`
 }
 
 // ImageSubscription defines a subscription to an image repository.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 8c45c146..65cb39cb 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -369,6 +369,62 @@ func (in *FreightList) DeepCopyObject() runtime.Object {
 	return nil
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FreightReference) DeepCopyInto(out *FreightReference) {
+	*out = *in
+	if in.Commits != nil {
+		in, out := &in.Commits, &out.Commits
+		*out = make([]GitCommit, len(*in))
+		copy(*out, *in)
+	}
+	if in.Images != nil {
+		in, out := &in.Images, &out.Images
+		*out = make([]Image, len(*in))
+		copy(*out, *in)
+	}
+	if in.Charts != nil {
+		in, out := &in.Charts, &out.Charts
+		*out = make([]Chart, len(*in))
+		copy(*out, *in)
+	}
+	if in.VerificationInfo != nil {
+		in, out := &in.VerificationInfo, &out.VerificationInfo
+		*out = new(VerificationInfo)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FreightReference.
+func (in *FreightReference) DeepCopy() *FreightReference {
+	if in == nil {
+		return nil
+	}
+	out := new(FreightReference)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in FreightReferenceStack) DeepCopyInto(out *FreightReferenceStack) {
+	{
+		in := &in
+		*out = make(FreightReferenceStack, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FreightReferenceStack.
+func (in FreightReferenceStack) DeepCopy() FreightReferenceStack {
+	if in == nil {
+		return nil
+	}
+	out := new(FreightReferenceStack)
+	in.DeepCopyInto(out)
+	return *out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *FreightStatus) DeepCopyInto(out *FreightStatus) {
 	*out = *in
@@ -665,6 +721,99 @@ func (in *KustomizePromotionMechanism) DeepCopy() *KustomizePromotionMechanism {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Project) DeepCopyInto(out *Project) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	if in.Spec != nil {
+		in, out := &in.Spec, &out.Spec
+		*out = new(ProjectSpec)
+		**out = **in
+	}
+	out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Project.
+func (in *Project) DeepCopy() *Project {
+	if in == nil {
+		return nil
+	}
+	out := new(Project)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Project) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProjectList) DeepCopyInto(out *ProjectList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]Project, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectList.
+func (in *ProjectList) DeepCopy() *ProjectList {
+	if in == nil {
+		return nil
+	}
+	out := new(ProjectList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ProjectList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProjectSpec) DeepCopyInto(out *ProjectSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectSpec.
+func (in *ProjectSpec) DeepCopy() *ProjectSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ProjectSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProjectStatus) DeepCopyInto(out *ProjectStatus) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProjectStatus.
+func (in *ProjectStatus) DeepCopy() *ProjectStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(ProjectStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Promotion) DeepCopyInto(out *Promotion) {
 	*out = *in
@@ -917,62 +1066,6 @@ func (in *RepoSubscription) DeepCopy() *RepoSubscription {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *SimpleFreight) DeepCopyInto(out *SimpleFreight) {
-	*out = *in
-	if in.Commits != nil {
-		in, out := &in.Commits, &out.Commits
-		*out = make([]GitCommit, len(*in))
-		copy(*out, *in)
-	}
-	if in.Images != nil {
-		in, out := &in.Images, &out.Images
-		*out = make([]Image, len(*in))
-		copy(*out, *in)
-	}
-	if in.Charts != nil {
-		in, out := &in.Charts, &out.Charts
-		*out = make([]Chart, len(*in))
-		copy(*out, *in)
-	}
-	if in.VerificationInfo != nil {
-		in, out := &in.VerificationInfo, &out.VerificationInfo
-		*out = new(VerificationInfo)
-		**out = **in
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SimpleFreight.
-func (in *SimpleFreight) DeepCopy() *SimpleFreight {
-	if in == nil {
-		return nil
-	}
-	out := new(SimpleFreight)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in SimpleFreightStack) DeepCopyInto(out *SimpleFreightStack) {
-	{
-		in := &in
-		*out = make(SimpleFreightStack, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SimpleFreightStack.
-func (in SimpleFreightStack) DeepCopy() SimpleFreightStack {
-	if in == nil {
-		return nil
-	}
-	out := new(SimpleFreightStack)
-	in.DeepCopyInto(out)
-	return *out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Stage) DeepCopyInto(out *Stage) {
 	*out = *in
@@ -1071,12 +1164,12 @@ func (in *StageStatus) DeepCopyInto(out *StageStatus) {
 	*out = *in
 	if in.CurrentFreight != nil {
 		in, out := &in.CurrentFreight, &out.CurrentFreight
-		*out = new(SimpleFreight)
+		*out = new(FreightReference)
 		(*in).DeepCopyInto(*out)
 	}
 	if in.History != nil {
 		in, out := &in.History, &out.History
-		*out = make(SimpleFreightStack, len(*in))
+		*out = make(FreightReferenceStack, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
diff --git a/charts/kargo/README.md b/charts/kargo/README.md
index 5671df1a..85512099 100644
--- a/charts/kargo/README.md
+++ b/charts/kargo/README.md
@@ -20,18 +20,28 @@
 
 ### KubeConfigs
 
-Optionally point to Kubernetes Secrets containing kubeconfig for a remote
-Kubernetes cluster hosting Kargo resources and/or a remote Kubernetes cluster
-hosting Argo CD resources. This is useful for cases where the Kargo
-controller is running somewhere other than the cluster(s) it is managing. The
-config for Kargo and Argo CD can be the same, different, or omitted entirely.
-When omitted, the controller will fall back on in-cluster configuration.
-In the average case, these settings should be left alone.
-
-| Name                       | Description                                                                                              | Value       |
-| -------------------------- | -------------------------------------------------------------------------------------------------------- | ----------- |
-| `kubeconfigSecrets.kargo`  | Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Kargo resources   | `undefined` |
-| `kubeconfigSecrets.argocd` | Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Argo CD resources | `undefined` |
+Optionally point to Kubernetes Secrets containing kubeconfig for:
+
+1. A remote cluster hosting Kargo resources
+
+2. A remote cluster hosting Argo CD resources
+
+3. A remote cluster that is running Argo Rollouts and is a suitable location
+to execute user-defined verification processes in the form of Argo
+Rollouts AnalysisRuns
+
+This flexibility is useful for various advanced use cases -- especially
+topologies where Kargo data may be sharded, with Kargo controllers distributed
+across many clusters. Any two, or even all three, of these configurations may
+be the same. In the average case, these should all be left unspecified. All
+that are unspecified will default to configuration for the cluster in which
+the Kargo controller is running.
+
+| Name                         | Description                                                                                                                                                   | Value       |
+| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
+| `kubeconfigSecrets.kargo`    | Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Kargo resources. Used by all Kargo components.                         | `undefined` |
+| `kubeconfigSecrets.argocd`   | Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Argo CD resources. Used by Kargo controller(s) only.                   | `undefined` |
+| `kubeconfigSecrets.rollouts` | Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster that can execute Argo Rollouts AnalysisRuns. Used by Kargo controller(s) only. | `undefined` |
 
 ### API
 
@@ -97,6 +107,17 @@ In the average case, these settings should be left alone.
 | `controller.tolerations`                      | Tolerations for controller pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `[]`        |
 | `controller.affinity`                         | Specifies pod affinity for controller pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `{}`        |
 
+### Management Controller
+
+| Name                                | Description                                                             | Value  |
+| ----------------------------------- | ----------------------------------------------------------------------- | ------ |
+| `managementController.enabled`      | Whether the management controller is enabled.                           | `true` |
+| `managementController.logLevel`     | The log level for the management controller.                            | `INFO` |
+| `managementController.resources`    | Resources limits and requests for the management controller containers. | `{}`   |
+| `managementController.nodeSelector` | Node selector for management controller pods.                           | `{}`   |
+| `managementController.tolerations`  | Tolerations for management controller pods.                             | `[]`   |
+| `managementController.affinity`     | Specifies pod affinity for management controller pods.                  | `{}`   |
+
 ### Webhooks
 
 | Name                | Description                                                                                      | Value  |
diff --git a/charts/kargo/crds/kargo.akuity.io_projects.yaml b/charts/kargo/crds/kargo.akuity.io_projects.yaml
new file mode 100644
index 00000000..e9409be1
--- /dev/null
+++ b/charts/kargo/crds/kargo.akuity.io_projects.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.13.0
+  name: projects.kargo.akuity.io
+spec:
+  group: kargo.akuity.io
+  names:
+    kind: Project
+    listKind: ProjectList
+    plural: projects
+    singular: project
+  scope: Cluster
+  versions:
+  - additionalPrinterColumns:
+    - jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: 'Project is a resource type that reconciles to a specially labeled
+          namespace and other TODO: TBD project-level resources.'
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation
+              of an object. Servers should convert recognized schemas to the latest
+              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this
+              object represents. Servers may infer this from the endpoint the client
+              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: Spec describes a Project.
+            type: object
+          status:
+            description: Status describes the Project's current status.
+            type: object
+        required:
+        - spec
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/charts/kargo/crds/kargo.akuity.io_stages.yaml b/charts/kargo/crds/kargo.akuity.io_stages.yaml
index 070861a5..16a036b9 100644
--- a/charts/kargo/crds/kargo.akuity.io_stages.yaml
+++ b/charts/kargo/crds/kargo.akuity.io_stages.yaml
@@ -310,6 +310,12 @@ spec:
                                 type: object
                               type: array
                           type: object
+                        insecureSkipTLSVerify:
+                          description: InsecureSkipTLSVerify specifies whether certificate
+                            verification errors should be ignored when connecting
+                            to the repository. This should be enabled only with great
+                            caution.
+                          type: boolean
                         kustomize:
                           description: Kustomize describes how to use Kustomize to
                             incorporate Freight into the Stage. This is mutually exclusive
@@ -817,8 +823,8 @@ spec:
                 description: History is a stack of recent Freight. By default, the
                   last ten Freight are stored.
                 items:
-                  description: SimpleFreight is a simplified representation of a piece
-                    of Freight -- not a root resource type.
+                  description: FreightReference is a simplified representation of
+                    a piece of Freight -- not a root resource type.
                   properties:
                     charts:
                       description: Charts describes specific versions of specific
diff --git a/charts/kargo/crds/kargo.akuity.io_warehouses.yaml b/charts/kargo/crds/kargo.akuity.io_warehouses.yaml
index 751c1855..3e8cb02c 100644
--- a/charts/kargo/crds/kargo.akuity.io_warehouses.yaml
+++ b/charts/kargo/crds/kargo.akuity.io_warehouses.yaml
@@ -83,6 +83,12 @@ spec:
                           minLength: 1
                           pattern: ^\w+([-/]\w+)*$
                           type: string
+                        insecureSkipTLSVerify:
+                          description: InsecureSkipTLSVerify specifies whether certificate
+                            verification errors should be ignored when connecting
+                            to the repository. This should be enabled only with great
+                            caution.
+                          type: boolean
                         repoURL:
                           description: URL is the repository's URL. This is a required
                             field.
diff --git a/charts/kargo/templates/_helpers.tpl b/charts/kargo/templates/_helpers.tpl
index 019444b0..07f2e991 100644
--- a/charts/kargo/templates/_helpers.tpl
+++ b/charts/kargo/templates/_helpers.tpl
@@ -46,18 +46,22 @@ app.kubernetes.io/instance: {{ .Release.Name }}
 app.kubernetes.io/component: api
 {{- end -}}
 
-{{- define "kargo.dexServer.labels" -}}
-app.kubernetes.io/component: dex-server
-{{- end -}}
-
 {{- define "kargo.controller.labels" -}}
 app.kubernetes.io/component: controller
 {{- end -}}
 
+{{- define "kargo.dexServer.labels" -}}
+app.kubernetes.io/component: dex-server
+{{- end -}}
+
 {{- define "kargo.garbageCollector.labels" -}}
 app.kubernetes.io/component: garbage-collector
 {{- end -}}
 
+{{- define "kargo.managementController.labels" -}}
+app.kubernetes.io/component: management-controller
+{{- end -}}
+
 {{- define "kargo.webhooksServer.labels" -}}
 app.kubernetes.io/component: webhooks-server
 {{- end -}}
diff --git a/charts/kargo/templates/controller/configmap.yaml b/charts/kargo/templates/controller/configmap.yaml
index a1850f06..04ca9b28 100644
--- a/charts/kargo/templates/controller/configmap.yaml
+++ b/charts/kargo/templates/controller/configmap.yaml
@@ -21,6 +21,9 @@ data:
   {{- if .Values.kubeconfigSecrets.argocd }}
   ARGOCD_KUBECONFIG: /etc/kargo/kubeconfigs/argocd-kubeconfig.yaml
   {{- end }}
+  {{- if .Values.kubeconfigSecrets.rollouts }}
+  ROLLOUTS_KUBECONFIG: /etc/kargo/kubeconfigs/rollouts-kubeconfig.yaml
+  {{- end }}
   ARGOCD_NAMESPACE: {{ .Values.controller.argocd.namespace }}
   ARGOCD_ENABLE_CREDENTIAL_BORROWING: {{ quote .Values.controller.argocd.enableCredentialBorrowing }}
   ARGOCD_WATCH_ARGOCD_NAMESPACE_ONLY: {{ quote .Values.controller.argocd.watchArgocdNamespaceOnly }}
diff --git a/charts/kargo/templates/controller/deployment.yaml b/charts/kargo/templates/controller/deployment.yaml
index d5ba07db..01998c89 100644
--- a/charts/kargo/templates/controller/deployment.yaml
+++ b/charts/kargo/templates/controller/deployment.yaml
@@ -36,7 +36,7 @@ spec:
         envFrom:
         - configMapRef:
             name: kargo-controller
-        {{- if or .Values.kubeconfigSecrets.kargo .Values.kubeconfigSecrets.argocd }}
+        {{- if or .Values.kubeconfigSecrets.kargo .Values.kubeconfigSecrets.argocd .Values.kubeconfigSecrets.rollouts }}
         volumeMounts:
         - mountPath: /etc/kargo/kubeconfigs
           name: kubeconfigs
@@ -44,12 +44,12 @@ spec:
         {{- end }}
         resources:
           {{- toYaml .Values.controller.resources | nindent 10 }}
-      {{- if or .Values.kubeconfigSecrets.kargo .Values.kubeconfigSecrets.argocd }}
+      {{- if or .Values.kubeconfigSecrets.kargo .Values.kubeconfigSecrets.argocd .Values.kubeconfigSecrets.rollouts }}
       volumes:
       - name: kubeconfigs
         projected:
           sources:
-          {{- if or .Values.kubeconfigSecrets.kargo }}
+          {{- if .Values.kubeconfigSecrets.kargo }}
           - secret:
               name: {{ .Values.kubeconfigSecrets.kargo }}
               items:
@@ -57,7 +57,7 @@ spec:
                 path: kubeconfig.yaml
                 mode: 0644
           {{- end }}
-          {{- if or .Values.kubeconfigSecrets.argocd }}
+          {{- if .Values.kubeconfigSecrets.argocd }}
           - secret:
               name: {{ .Values.kubeconfigSecrets.argocd }}
               items:
@@ -65,6 +65,14 @@ spec:
                 path: argocd-kubeconfig.yaml
                 mode: 0644
           {{- end }}
+          {{- if .Values.kubeconfigSecrets.rollouts }}
+          - secret:
+              name: {{ .Values.kubeconfigSecrets.rollouts }}
+              items:
+              - key: kubeconfig.yaml
+                path: rollouts-kubeconfig.yaml
+                mode: 0644
+          {{- end }}
       {{- end }}
       {{- with .Values.controller.nodeSelector }}
       nodeSelector:
diff --git a/charts/kargo/templates/management-controller/cluster-role-bindings.yaml b/charts/kargo/templates/management-controller/cluster-role-bindings.yaml
new file mode 100644
index 00000000..64cfaace
--- /dev/null
+++ b/charts/kargo/templates/management-controller/cluster-role-bindings.yaml
@@ -0,0 +1,17 @@
+{{- if and .Values.managementController.enabled .Values.rbac.installClusterRoleBindings }}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: kargo-management-controller
+  labels:
+    {{- include "kargo.labels" . | nindent 4 }}
+    {{- include "kargo.managementController.labels" . | nindent 4 }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: kargo-management-controller
+subjects:
+- kind: ServiceAccount
+  namespace: {{ .Release.Namespace }}
+  name: kargo-management-controller
+{{- end }}
diff --git a/charts/kargo/templates/management-controller/cluster-roles.yaml b/charts/kargo/templates/management-controller/cluster-roles.yaml
new file mode 100644
index 00000000..2cc3ced8
--- /dev/null
+++ b/charts/kargo/templates/management-controller/cluster-roles.yaml
@@ -0,0 +1,18 @@
+{{- if and .Values.managementController.enabled .Values.rbac.installClusterRoles }}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: kargo-management-controller
+  labels:
+    {{- include "kargo.labels" . | nindent 4 }}
+    {{- include "kargo.managementController.labels" . | nindent 4 }}
+rules:
+- apiGroups:
+  - kargo.akuity.io
+  resources:
+  - projects
+  verbs:
+  - get
+  - list
+  - watch
+{{- end }}  
diff --git a/charts/kargo/templates/management-controller/configmap.yaml b/charts/kargo/templates/management-controller/configmap.yaml
new file mode 100644
index 00000000..d9cc5b92
--- /dev/null
+++ b/charts/kargo/templates/management-controller/configmap.yaml
@@ -0,0 +1,15 @@
+{{- if .Values.managementController.enabled }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: kargo-management-controller
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "kargo.labels" . | nindent 4 }}
+    {{- include "kargo.managementController.labels" . | nindent 4 }}
+data:
+  LOG_LEVEL: {{ .Values.managementController.logLevel }}
+  {{- if .Values.kubeconfigSecrets.kargo }}
+  KUBECONFIG: /etc/kargo/kubeconfigs/kubeconfig.yaml
+  {{- end }}
+{{- end }}
diff --git a/charts/kargo/templates/management-controller/deployment.yaml b/charts/kargo/templates/management-controller/deployment.yaml
new file mode 100644
index 00000000..c4caced5
--- /dev/null
+++ b/charts/kargo/templates/management-controller/deployment.yaml
@@ -0,0 +1,67 @@
+{{- if .Values.managementController.enabled }}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: kargo-management-controller
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "kargo.labels" . | nindent 4 }}
+    {{- include "kargo.managementController.labels" . | nindent 4 }}
+spec:
+  replicas: 1
+  strategy:
+    type: Recreate
+  selector:
+    matchLabels:
+      {{- include "kargo.selectorLabels" . | nindent 6 }}
+      {{- include "kargo.managementController.labels" . | nindent 6 }}
+  template:
+    metadata:
+      labels:
+        {{- include "kargo.selectorLabels" . | nindent 8 }}
+        {{- include "kargo.managementController.labels" . | nindent 8 }}
+      annotations:
+        configmap/checksum: {{ include (print $.Template.BasePath "/management-controller/configmap.yaml") . | sha256sum }}
+    spec:
+      serviceAccount: kargo-management-controller
+      {{- with .Values.managementController.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      containers:
+      - name: management-controller
+        image: {{ include "kargo.image" . }}
+        imagePullPolicy: {{ .Values.image.pullPolicy }}
+        command: ["/usr/local/bin/kargo", "management-controller"]
+        envFrom:
+        - configMapRef:
+            name: kargo-management-controller
+        {{- if .Values.kubeconfigSecrets.kargo }}
+        volumeMounts:
+        - mountPath: /etc/kargo/kubeconfigs
+          name: kubeconfigs
+          readOnly: true
+        {{- end }}
+        resources:
+          {{- toYaml .Values.managementController.resources | nindent 10 }}
+      {{- if .Values.kubeconfigSecrets.kargo }}
+      volumes:
+      - name: kubeconfigs
+        projected:
+          sources:
+          - secret:
+              name: {{ .Values.kubeconfigSecrets.kargo }}
+              items:
+              - key: kubeconfig.yaml
+                path: kubeconfig.yaml
+                mode: 0644
+      {{- end }}
+      {{- with .Values.managementController.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.managementController.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+{{- end }}
diff --git a/charts/kargo/templates/management-controller/service-account.yaml b/charts/kargo/templates/management-controller/service-account.yaml
new file mode 100644
index 00000000..258dd27d
--- /dev/null
+++ b/charts/kargo/templates/management-controller/service-account.yaml
@@ -0,0 +1,10 @@
+{{- if .Values.managementController.enabled }}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: kargo-management-controller
+  namespace: {{ .Release.Namespace }}
+  labels:
+    {{- include "kargo.labels" . | nindent 4 }}
+    {{- include "kargo.managementController.labels" . | nindent 4 }}
+{{- end }}
diff --git a/charts/kargo/values.yaml b/charts/kargo/values.yaml
index 29649cf4..ea53711d 100755
--- a/charts/kargo/values.yaml
+++ b/charts/kargo/values.yaml
@@ -21,20 +21,31 @@ rbac:
 
 ## @section KubeConfigs
 ## @descriptionStart
-## Optionally point to Kubernetes Secrets containing kubeconfig for a remote
-## Kubernetes cluster hosting Kargo resources and/or a remote Kubernetes cluster
-## hosting Argo CD resources. This is useful for cases where the Kargo
-## controller is running somewhere other than the cluster(s) it is managing. The
-## config for Kargo and Argo CD can be the same, different, or omitted entirely.
-## When omitted, the controller will fall back on in-cluster configuration.
-## In the average case, these settings should be left alone.
+## Optionally point to Kubernetes Secrets containing kubeconfig for:
+##
+## 1. A remote cluster hosting Kargo resources
+##
+## 2. A remote cluster hosting Argo CD resources
+##
+## 3. A remote cluster that is running Argo Rollouts and is a suitable location
+##    to execute user-defined verification processes in the form of Argo
+##    Rollouts AnalysisRuns
+##
+## This flexibility is useful for various advanced use cases -- especially
+## topologies where Kargo data may be sharded, with Kargo controllers distributed
+## across many clusters. Any two, or even all three, of these configurations may
+## be the same. In the average case, these should all be left unspecified. All
+## that are unspecified will default to configuration for the cluster in which
+## the Kargo controller is running.
 ## @descriptionEnd
 ## @skip kubeconfigSecrets
 kubeconfigSecrets: {}
-  ## @param kubeconfigSecrets.kargo [nullable] Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Kargo resources
+  ## @param kubeconfigSecrets.kargo [nullable] Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Kargo resources. Used by all Kargo components.
   # kargo: ""
-  ## @param kubeconfigSecrets.argocd [nullable] Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Argo CD resources
+  ## @param kubeconfigSecrets.argocd [nullable] Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster hosting Argo CD resources. Used by Kargo controller(s) only.
   # argocd: ""
+  ## @param kubeconfigSecrets.rollouts [nullable] Kubernetes `Secret` name containing kubeconfig for a remote Kubernetes cluster that can execute Argo Rollouts AnalysisRuns. Used by Kargo controller(s) only.
+  # rollouts: ""
 
 ## @section API
 api:
@@ -232,6 +243,30 @@ controller:
   ## @param controller.affinity Specifies pod affinity for controller pods.
   affinity: {}
 
+## @section Management Controller
+## All settings for the management controller component
+managementController:
+  ## @param managementController.enabled Whether the management controller is enabled.
+  enabled: true
+
+  ## @param managementController.logLevel The log level for the management controller.
+  logLevel: INFO
+
+  ## @param managementController.resources Resources limits and requests for the management controller containers.
+  resources: {}
+    # limits:
+    #   cpu: 100m
+    #   memory: 128Mi
+    # requests:
+    #   cpu: 100m
+    #   memory: 128Mi
+  ## @param managementController.nodeSelector Node selector for management controller pods.
+  nodeSelector: {}
+  ## @param managementController.tolerations Tolerations for management controller pods.
+  tolerations: []
+  ## @param managementController.affinity Specifies pod affinity for management controller pods.
+  affinity: {}
+
 ## @section Webhooks
 webhooks:
   ## @param webhooks.register Whether to create `ValidatingWebhookConfiguration` and `MutatingWebhookConfiguration` resources.
diff --git a/cmd/cli/root.go b/cmd/cli/root.go
index 48b9c0ce..f7426172 100644
--- a/cmd/cli/root.go
+++ b/cmd/cli/root.go
@@ -21,6 +21,7 @@ import (
 	"github.com/akuity/kargo/internal/cli/cmd/delete"
 	"github.com/akuity/kargo/internal/cli/cmd/get"
 	"github.com/akuity/kargo/internal/cli/cmd/login"
+	"github.com/akuity/kargo/internal/cli/cmd/logout"
 	"github.com/akuity/kargo/internal/cli/cmd/refresh"
 	"github.com/akuity/kargo/internal/cli/cmd/stage"
 	"github.com/akuity/kargo/internal/cli/cmd/update"
@@ -88,8 +89,8 @@ func NewRootCommand(opt *option.Option, rs *rootState) (*cobra.Command, error) {
 		return nil, err
 	}
 	opt.PrintFlags = genericclioptions.NewPrintFlags("").WithTypeSetter(scheme)
-	option.InsecureTLS(&opt.InsecureTLS)(cmd.PersistentFlags())
-	option.LocalServer(&opt.UseLocalServer)(cmd.PersistentFlags())
+	option.InsecureTLS(cmd.PersistentFlags(), opt)
+	option.LocalServer(cmd.PersistentFlags(), opt)
 
 	cmd.AddCommand(apply.NewCommand(opt))
 	cmd.AddCommand(cliconfigcmd.NewCommand())
@@ -97,6 +98,7 @@ func NewRootCommand(opt *option.Option, rs *rootState) (*cobra.Command, error) {
 	cmd.AddCommand(delete.NewCommand(opt))
 	cmd.AddCommand(get.NewCommand(opt))
 	cmd.AddCommand(login.NewCommand(opt))
+	cmd.AddCommand(logout.NewCommand())
 	cmd.AddCommand(stage.NewCommand(opt))
 	cmd.AddCommand(refresh.NewCommand(opt))
 	cmd.AddCommand(update.NewCommand(opt))
diff --git a/cmd/controlplane/controller.go b/cmd/controlplane/controller.go
index cad4c37c..11126737 100644
--- a/cmd/controlplane/controller.go
+++ b/cmd/controlplane/controller.go
@@ -53,6 +53,14 @@ func newControllerCommand() *cobra.Command {
 
 			var kargoMgr manager.Manager
 			{
+				// If the env var is undefined, this will resolve to kubeconfig for the
+				// cluster the controller is running in.
+				//
+				// It is typically defined if this controller is running somewhere other
+				// than where the Kargo resources live. One example of this would be a
+				// sharded topology wherein Kargo controllers run on application
+				// clusters, with Kargo resources hosted in a centralized management
+				// cluster.
 				restCfg, err :=
 					kubernetes.GetRestConfig(ctx, os.GetEnv("KUBECONFIG", ""))
 				if err != nil {
@@ -98,6 +106,14 @@ func newControllerCommand() *cobra.Command {
 
 			var argocdMgr manager.Manager
 			{
+				// If the env var is undefined, this will resolve to kubeconfig for the
+				// cluster the controller is running in.
+				//
+				// It is typically defined if this controller is running somewhere other
+				// than where the Argo CD resources live. Two examples of this would
+				// involve topologies wherein Kargo controllers run EITHER sharded
+				// across application clusters OR in a centralized management cluster,
+				// but with Argo CD deployed to a different management cluster.
 				restCfg, err :=
 					kubernetes.GetRestConfig(ctx, os.GetEnv("ARGOCD_KUBECONFIG", ""))
 				if err != nil {
@@ -153,8 +169,16 @@ func newControllerCommand() *cobra.Command {
 
 			var rolloutsMgr manager.Manager
 			{
+				// If the env var is undefined, this will resolve to kubeconfig for the
+				// cluster the controller is running in.
+				//
+				// It is typically defined if this controller is running somewhere other
+				// than a cluster suitable for executing Argo Rollouts AnalysesRuns and
+				// user-defined workloads. An example of this would be a a topology
+				// wherein the Kargo and Argo CD controllers both run in management
+				// clusters, that are a not suitable for for this purpose.
 				restCfg, err :=
-					kubernetes.GetRestConfig(ctx, os.GetEnv("ARGOCD_KUBECONFIG", ""))
+					kubernetes.GetRestConfig(ctx, os.GetEnv("ROLLOUTS_KUBECONFIG", ""))
 				if err != nil {
 					return errors.Wrap(
 						err,
diff --git a/cmd/controlplane/management_controller.go b/cmd/controlplane/management_controller.go
new file mode 100644
index 00000000..10ea29e4
--- /dev/null
+++ b/cmd/controlplane/management_controller.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"sigs.k8s.io/controller-runtime/pkg/metrics/server"
+
+	kargoapi "github.com/akuity/kargo/api/v1alpha1"
+	"github.com/akuity/kargo/internal/api/kubernetes"
+	"github.com/akuity/kargo/internal/controller/management/projects"
+	"github.com/akuity/kargo/internal/os"
+	versionpkg "github.com/akuity/kargo/internal/version"
+)
+
+func newManagementControllerCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:               "management-controller",
+		DisableAutoGenTag: true,
+		SilenceErrors:     true,
+		SilenceUsage:      true,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			ctx := cmd.Context()
+
+			version := versionpkg.GetVersion()
+
+			log.WithFields(log.Fields{
+				"version": version.Version,
+				"commit":  version.GitCommit,
+			}).Info("Starting Kargo Management Controller")
+
+			var kargoMgr manager.Manager
+			{
+				restCfg, err :=
+					kubernetes.GetRestConfig(ctx, os.GetEnv("KUBECONFIG", ""))
+				if err != nil {
+					return errors.Wrap(
+						err,
+						"error loading REST config for Kargo controller manager",
+					)
+				}
+				restCfg.ContentType = runtime.ContentTypeJSON
+
+				scheme := runtime.NewScheme()
+				if err = corev1.AddToScheme(scheme); err != nil {
+					return errors.Wrap(
+						err,
+						"error adding Kubernetes core API to Kargo controller manager "+
+							"scheme",
+					)
+				}
+				if err = kargoapi.AddToScheme(scheme); err != nil {
+					return errors.Wrap(
+						err,
+						"error adding Kargo API to Kargo controller manager scheme",
+					)
+				}
+				if kargoMgr, err = ctrl.NewManager(
+					restCfg,
+					ctrl.Options{
+						Scheme: scheme,
+						Metrics: server.Options{
+							BindAddress: "0",
+						},
+					},
+				); err != nil {
+					return errors.Wrap(err, "error initializing Kargo controller manager")
+				}
+			}
+
+			if err := projects.SetupReconcilerWithManager(kargoMgr); err != nil {
+				return errors.Wrap(err, "error setting up Projects reconciler")
+			}
+
+			return errors.Wrap(kargoMgr.Start(ctx), "error starting kargo manager")
+		},
+	}
+}
diff --git a/cmd/controlplane/root.go b/cmd/controlplane/root.go
index 0b14e6cf..ac10eac6 100644
--- a/cmd/controlplane/root.go
+++ b/cmd/controlplane/root.go
@@ -22,6 +22,7 @@ func Execute(ctx context.Context) error {
 	rootCmd.AddCommand(newAPICommand())
 	rootCmd.AddCommand(newControllerCommand())
 	rootCmd.AddCommand(newGarbageCollectorCommand())
+	rootCmd.AddCommand(newManagementControllerCommand())
 	rootCmd.AddCommand(newVersionCommand())
 	rootCmd.AddCommand(newWebhooksServerCommand())
 	return rootCmd.ExecuteContext(ctx)
diff --git a/docs/docs/20-quickstart.mdx b/docs/docs/20-quickstart.mdx
index 1e1cee94..a8db79b3 100644
--- a/docs/docs/20-quickstart.mdx
+++ b/docs/docs/20-quickstart.mdx
@@ -320,7 +320,6 @@ the previous section.
         kargo.akuity.io/secret-type: repository
     stringData:
       type: git
-      project: default
       url: ${GITOPS_REPO_URL}
       username: ${GITHUB_USERNAME}
       password: ${GITHUB_PAT}
diff --git a/docs/docs/30-how-to-guides/15-working-with-freight.md b/docs/docs/30-how-to-guides/15-working-with-freight.md
new file mode 100644
index 00000000..a0887ab2
--- /dev/null
+++ b/docs/docs/30-how-to-guides/15-working-with-freight.md
@@ -0,0 +1,212 @@
+---
+description: Learn how to work effectively with freight
+sidebar_label: Working with freight
+---
+
+# Working With Freight
+
+**Freight** is an important Kargo concept. A single "piece of freight" is a set
+of references to one or more versioned artifacts, which may include one or more:
+
+* Container images (from image repositories)
+
+* Kubernetes manifests (from Git repositories)
+
+* Helm charts (from chart repositories)
+
+Freight can therefore be thought of as a sort of meta-artifact. Freight is what
+Kargo seeks to progress from one stage to another.
+
+:::info
+To learn the fundamentals of freight and the warehouses that produce freight,
+visit the [concepts doc](./concepts).
+:::
+
+The remainder of this page describes features of freight that will enabled you
+to work more effectively.
+
+## IDs
+
+Like all Kubernetes resources, Kargo `Freight` resources have a `metadata.name`
+field, which uniquely identifies each resource of that type within a given Kargo
+project (a specially labeled Kubernetes namespace). When a `Warehouse` produces
+a new `Freight` resource, it will compute a canonical representation of the
+artifacts referenced by that resource and use that, in turn, to compute a SHA-1
+hash. This becomes the value of both the resources's `metadata.name` _and_ `id`
+fields. The deterministic method of computing this value makes it a unique
+"fingerprint" of the collection of artifacts referenced by the `Freight`
+resource.
+
+:::info
+Why include the same information in two different fields?
+
+The Kargo team anticipates the possibility of someday permitting users to
+manually create their own `Freight` resources -- i.e. `Freight` resources not
+created by a `Warehouse`. In such a scenario, the value of the `metadata.name`
+field would be user-defined and would not be guaranteed to reflect the artifacts
+referenced by the resource.
+
+Because Kargo utilizes a
+[mutating admission webhooks](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook)
+to ensure that the value of the `id` field is _always_ set correctly, Kargo can
+rely on the value of that field to _always_ reflect the artifacts referenced by
+the resource, even when the `metadata.name` field does not.
+:::
+
+## Aliases
+
+While the `id` field (and `metadata.name` field, for now) contain predictably
+computed SHA-1 hashes, such identifiers are, unarguably, not very user-friendly.
+To make `Freight` resources easier for human users to identify, `Warehouse`s
+automatically generate a human-friendly alias for every `Freight` resource they
+produce and apply it as the value of the `Freight` resource's
+`kargo.akuity.io/alias`
+[label](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/).
+
+:::info
+Generating aliases of the form `<adjective>-<animal>` is a strategy borrowed
+from Docker, which generates similar names for containers not explicitly named
+by users.
+:::
+
+:::info
+Why a label?
+
+Kubernetes enforces the immutability of the `metadata.name` field for all
+resources and a Kargo webhook enforces the immutability of the `id` field.
+
+Kubernetes
+[labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/),
+by contrast, are both mutable and indexed, which which makes them ideal for use
+as secondary identifiers.
+:::
+
+When using the Kargo CLI to query for `Freight` resources, the `alias` field is
+always displayed:
+
+```shell
+kargo get freight --project kargo-demo
+```
+
+Sample output:
+
+```shell
+NAME/ID                                    ALIAS              AGE
+f5f87aa23c9e97f43eb83dd63768ee41f5ba3766   mortal-dragonfly   35s
+```
+
+It is also displayed when using `kubectl` to query for `Freight` resources:
+
+```shell
+kubectl get freight --namespace kargo-demo
+```
+
+Sample output:
+
+```shell
+NAME                                       ALIAS              AGE
+f5f87aa23c9e97f43eb83dd63768ee41f5ba3766   mortal-dragonfly   35s
+```
+
+:::info
+The Kargo UI, to make efficient use of screen real estate, displays aliases
+only, but a `Freight` resource's `id` can always be discovered by hovering over
+its alias.
+:::
+
+:::note
+Some, but not all, Kargo CLI commands will accepts `Freight` aliases as an
+alternative to `Freights` IDs.
+
+Eventually, all Kargo CLI commands will accept aliases as an alternative.
+:::
+
+### Updating Aliases
+
+While every `Freight` resource is automatically assigned an alias, users may
+sometimes wish to override that alias with one of their own choosing. This can
+make it easier to identify a particularly important (or problematic) `Freight`
+resource as it progresses through the `Stage`s of a pipeline.
+
+This is conveniently accomplished via the Kargo CLI:
+
+```shell
+kargo update freight-alias \
+  f5f87aa23c9e97f43eb83dd63768ee41f5ba3766 \
+  frozen-tauntaun \
+  --project kargo-demo
+```
+
+This can also be accomplished via `kubectl` commands `apply`, `edit`, `patch`,
+etc.
+
+:::note
+Aliases cannot currently be updated via the Kargo UI, but this will be addressed
+in the `v0.4.0` release.
+:::
+
+## Manual Approvals
+
+The [concepts doc](http://localhost:3000/concepts#verifications) describes the
+usual process by which `Freight` resources are _verified_ at each `Stage` in a
+pipeline before becoming available to the next `Stage` or `Stage`s. In brief, it
+typically requires the `Stage` to reach a healthy state _and_, if applicable,
+any user-defined verification processes to complete with favorable results.
+
+This is suitable for the average case wherein a new `Freight` resource is
+expected to traverse the entirety of a pipeline on its way to production,
+however, it is nearly inevitable that the occasional need for a "hotfix" will
+arise, in which case it may sometimes be desirable to bypass one or more
+`Stage`s in the pipeline.
+
+To enable this, Kargo provides the ability to manually approve a `Freight`
+resource for promotion to any given `Stage`. At present, this can be
+accomplished only through the Kargo UI:
+
+1. Click the three dots in the upper-right corner of any `Freight` resource
+   select __Manually Approve__.
+
+1. Click on the `Stage` resource for which you wish to approve the `Freight`.
+
+:::note
+Manual approvals cannot currently be granted via the Kargo CLI, but this will be
+addressed in the `v0.4.0` release.
+
+Manual approvals also cannot be granted via `kubectl` due to technical factors
+preventing `kubectl` from updating `status` subresources of Kargo resources.
+:::
+
+:::note
+Manually granting approval for a `Freight` resource to be promoted to any given
+`Stage` requires the same level of permissions as would be required to carry out
+that promotion, although, granting manual approval does _not_ automatically
+create a corresponding `Promotion` resource.
+:::
+
+After successfully granting manual approval for a `Freight` resource to be
+promoted to a given `Stage`, the `Freight` resource's `status` field will
+reflect that approval.
+
+The following depicts a `Freight` resource that has been verified in a `test`
+`Stage` through the usual process, but has been manually approved for promotion
+to the `prod` `Stage`. i.e. Any `Stage`s between `test` and `prod` may be
+bypassed.
+
+```shell
+kargo get freight \
+  --project kargo-demo \
+  --output jsonpath-as-json={.status}
+```
+
+```shell
+[
+    {
+        "approvedFor": {
+            "prod": {}
+        },
+        "verifiedIn": {
+            "test": {}
+        }
+    }
+]
+```
diff --git a/go.mod b/go.mod
index b02eccdd..d6ebd897 100644
--- a/go.mod
+++ b/go.mod
@@ -25,7 +25,6 @@ require (
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pkg/errors v0.9.1
 	github.com/rs/cors v1.10.1
-	github.com/samber/mo v1.11.0
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/pflag v1.0.5
@@ -37,7 +36,7 @@ require (
 	golang.org/x/exp v0.0.0-20230807204917-050eac23e9de
 	golang.org/x/net v0.19.0
 	golang.org/x/oauth2 v0.15.0
-	golang.org/x/sync v0.5.0
+	golang.org/x/sync v0.6.0
 	google.golang.org/grpc v1.60.1
 	google.golang.org/protobuf v1.32.0
 	gopkg.in/yaml.v3 v3.0.1
diff --git a/go.sum b/go.sum
index 2661e907..c327d28e 100644
--- a/go.sum
+++ b/go.sum
@@ -314,8 +314,6 @@ github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
 github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/samber/mo v1.11.0 h1:ZOiSkrGGpNhVv/1dxP02risztdMTIwE8KSW9OG4k5bY=
-github.com/samber/mo v1.11.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -419,8 +417,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
-golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/internal/api/types/v1alpha1/types.go b/internal/api/types/v1alpha1/types.go
index 6f344fa8..d65d2f59 100644
--- a/internal/api/types/v1alpha1/types.go
+++ b/internal/api/types/v1alpha1/types.go
@@ -62,13 +62,13 @@ func FromStageStatusProto(s *v1alpha1.StageStatus) *kargoapi.StageStatus {
 	if s == nil {
 		return nil
 	}
-	history := make(kargoapi.SimpleFreightStack, len(s.GetHistory()))
+	history := make(kargoapi.FreightReferenceStack, len(s.GetHistory()))
 	for idx, freight := range s.GetHistory() {
-		history[idx] = *FromSimpleFreightProto(freight)
+		history[idx] = *FromFreightReferenceProto(freight)
 	}
 	return &kargoapi.StageStatus{
 		Phase:          kargoapi.StagePhase(s.GetPhase()),
-		CurrentFreight: FromSimpleFreightProto(s.GetCurrentFreight()),
+		CurrentFreight: FromFreightReferenceProto(s.GetCurrentFreight()),
 		History:        history,
 		Health:         FromHealthProto(s.GetHealth()),
 		Error:          s.GetError(),
@@ -122,7 +122,7 @@ func FromFreightProto(f *v1alpha1.Freight) *kargoapi.Freight {
 	}
 }
 
-func FromSimpleFreightProto(s *v1alpha1.SimpleFreight) *kargoapi.SimpleFreight {
+func FromFreightReferenceProto(s *v1alpha1.FreightReference) *kargoapi.FreightReference {
 	if s == nil {
 		return nil
 	}
@@ -138,7 +138,7 @@ func FromSimpleFreightProto(s *v1alpha1.SimpleFreight) *kargoapi.SimpleFreight {
 	for idx, chart := range s.GetCharts() {
 		charts[idx] = *FromChartProto(chart)
 	}
-	return &kargoapi.SimpleFreight{
+	return &kargoapi.FreightReference{
 		ID:               s.GetId(),
 		Commits:          commits,
 		Images:           images,
@@ -678,13 +678,13 @@ func FromAnalysisRunReferenceProto(a *v1alpha1.AnalysisRunReference) *kargoapi.A
 
 func ToStageProto(e kargoapi.Stage) *v1alpha1.Stage {
 	// Status
-	var currentFreight *v1alpha1.SimpleFreight
+	var currentFreight *v1alpha1.FreightReference
 	if e.Status.CurrentFreight != nil {
-		currentFreight = ToSimpleFreightProto(*e.Status.CurrentFreight, nil)
+		currentFreight = ToFreightReferenceProto(*e.Status.CurrentFreight, nil)
 	}
-	history := make([]*v1alpha1.SimpleFreight, len(e.Status.History))
+	history := make([]*v1alpha1.FreightReference, len(e.Status.History))
 	for idx := range e.Status.History {
-		history[idx] = ToSimpleFreightProto(e.Status.History[idx], nil)
+		history[idx] = ToFreightReferenceProto(e.Status.History[idx], nil)
 	}
 	var health *v1alpha1.Health
 	if e.Status.Health != nil {
@@ -700,7 +700,7 @@ func ToStageProto(e kargoapi.Stage) *v1alpha1.Stage {
 	}
 	var currentPromotion *v1alpha1.PromotionInfo
 	if e.Status.CurrentPromotion != nil {
-		sf := kargoapi.SimpleFreight{
+		sf := kargoapi.FreightReference{
 			ID:      e.Status.CurrentPromotion.Freight.ID,
 			Commits: e.Status.CurrentPromotion.Freight.Commits,
 			Images:  e.Status.CurrentPromotion.Freight.Images,
@@ -708,7 +708,7 @@ func ToStageProto(e kargoapi.Stage) *v1alpha1.Stage {
 		}
 		currentPromotion = &v1alpha1.PromotionInfo{
 			Name:    e.Status.CurrentPromotion.Name,
-			Freight: ToSimpleFreightProto(sf, nil),
+			Freight: ToFreightReferenceProto(sf, nil),
 		}
 	}
 	return &v1alpha1.Stage{
@@ -1009,7 +1009,7 @@ func ToFreightProto(f kargoapi.Freight) *v1alpha1.Freight {
 	}
 }
 
-func ToSimpleFreightProto(s kargoapi.SimpleFreight, firstSeen *time.Time) *v1alpha1.SimpleFreight {
+func ToFreightReferenceProto(s kargoapi.FreightReference, firstSeen *time.Time) *v1alpha1.FreightReference {
 	var firstSeenProto *timestamppb.Timestamp
 	if firstSeen != nil {
 		firstSeenProto = timestamppb.New(*firstSeen)
@@ -1026,7 +1026,7 @@ func ToSimpleFreightProto(s kargoapi.SimpleFreight, firstSeen *time.Time) *v1alp
 	for idx := range s.Charts {
 		charts[idx] = ToChartProto(s.Charts[idx])
 	}
-	return &v1alpha1.SimpleFreight{
+	return &v1alpha1.FreightReference{
 		Id:               s.ID,
 		FirstSeen:        firstSeenProto,
 		Commits:          commits,
diff --git a/internal/cli/cmd/apply/apply.go b/internal/cli/cmd/apply/apply.go
index 5a6394ed..f8f1a815 100644
--- a/internal/cli/cmd/apply/apply.go
+++ b/internal/cli/cmd/apply/apply.go
@@ -18,12 +18,8 @@ import (
 	kargosvcapi "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type Flags struct {
-	Filenames []string
-}
-
 func NewCommand(opt *option.Option) *cobra.Command {
-	var flag Flags
+	var filenames []string
 	cmd := &cobra.Command{
 		Use:   "apply [--project=project] -f (FILENAME)",
 		Short: "Apply a resource from a file or from stdin",
@@ -33,11 +29,11 @@ kargo apply -f stage.yaml
 `,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
-			if len(flag.Filenames) == 0 {
+			if len(filenames) == 0 {
 				return errors.New("filename is required")
 			}
 
-			rawManifest, err := option.ReadManifests(flag.Filenames...)
+			rawManifest, err := option.ReadManifests(filenames...)
 			if err != nil {
 				return errors.Wrap(err, "read manifests")
 			}
@@ -118,6 +114,6 @@ kargo apply -f stage.yaml
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Filenames("apply", &flag.Filenames)(cmd.Flags())
+	option.Filenames(cmd.Flags(), &filenames, "apply")
 	return cmd
 }
diff --git a/internal/cli/cmd/create/create.go b/internal/cli/cmd/create/create.go
index 8f98dea9..e24c9009 100644
--- a/internal/cli/cmd/create/create.go
+++ b/internal/cli/cmd/create/create.go
@@ -18,12 +18,8 @@ import (
 	kargosvcapi "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type Flags struct {
-	Filenames []string
-}
-
 func NewCommand(opt *option.Option) *cobra.Command {
-	var flag Flags
+	var filenames []string
 	cmd := &cobra.Command{
 		Use:   "create [--project=project] -f (FILENAME)",
 		Short: "Create a resource from a file or from stdin",
@@ -36,11 +32,11 @@ kargo create project my-project
 `,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
-			if len(flag.Filenames) == 0 {
+			if len(filenames) == 0 {
 				return errors.New("filename is required")
 			}
 
-			manifest, err := option.ReadManifests(flag.Filenames...)
+			manifest, err := option.ReadManifests(filenames...)
 			if err != nil {
 				return errors.Wrap(err, "read manifests")
 			}
@@ -96,7 +92,7 @@ kargo create project my-project
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Filenames("create", &flag.Filenames)(cmd.Flags())
+	option.Filenames(cmd.Flags(), &filenames, "apply")
 
 	// Subcommands
 	cmd.AddCommand(newProjectCommand(opt))
diff --git a/internal/cli/cmd/delete/delete.go b/internal/cli/cmd/delete/delete.go
index fef6be33..cf383132 100644
--- a/internal/cli/cmd/delete/delete.go
+++ b/internal/cli/cmd/delete/delete.go
@@ -17,12 +17,8 @@ import (
 	kargosvcapi "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type Flags struct {
-	Filenames []string
-}
-
 func NewCommand(opt *option.Option) *cobra.Command {
-	var flag Flags
+	var filenames []string
 	cmd := &cobra.Command{
 		Use:   "delete [--project=project] -f (FILENAME)",
 		Short: "Delete resources by resources and names",
@@ -38,11 +34,11 @@ kargo delete -f stage.yaml
 `,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
-			if len(flag.Filenames) == 0 {
+			if len(filenames) == 0 {
 				return errors.New("filename is required")
 			}
 
-			manifest, err := option.ReadManifests(flag.Filenames...)
+			manifest, err := option.ReadManifests(filenames...)
 			if err != nil {
 				return errors.Wrap(err, "read manifests")
 			}
@@ -86,7 +82,7 @@ kargo delete -f stage.yaml
 			return goerrors.Join(deleteErrs...)
 		},
 	}
-	option.Filenames("delete", &flag.Filenames)(cmd.Flags())
+	option.Filenames(cmd.Flags(), &filenames, "apply")
 
 	// Subcommands
 	cmd.AddCommand(newProjectCommand(opt))
diff --git a/internal/cli/cmd/delete/project.go b/internal/cli/cmd/delete/project.go
index e54fadd1..bfc747d8 100644
--- a/internal/cli/cmd/delete/project.go
+++ b/internal/cli/cmd/delete/project.go
@@ -44,6 +44,6 @@ kargo delete project my-project
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/cmd/delete/stage.go b/internal/cli/cmd/delete/stage.go
index ccda32b2..a91dd7ab 100644
--- a/internal/cli/cmd/delete/stage.go
+++ b/internal/cli/cmd/delete/stage.go
@@ -50,6 +50,6 @@ kargo delete stage --project=my-project my-stage
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/cmd/delete/warehouse.go b/internal/cli/cmd/delete/warehouse.go
index b9e2957a..b892a5ba 100644
--- a/internal/cli/cmd/delete/warehouse.go
+++ b/internal/cli/cmd/delete/warehouse.go
@@ -55,6 +55,6 @@ kargo delete warehouse --project=my-project my-warehouse
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/cmd/get/freight.go b/internal/cli/cmd/get/freight.go
index 20b578f3..86247cd0 100644
--- a/internal/cli/cmd/get/freight.go
+++ b/internal/cli/cmd/get/freight.go
@@ -94,7 +94,7 @@ kargo get freight --project=my-project my-freight
 			return resErr
 		},
 	}
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	opt.PrintFlags.AddFlags(cmd)
 	return cmd
 }
diff --git a/internal/cli/cmd/get/promotions.go b/internal/cli/cmd/get/promotions.go
index 82a374b6..6812c7a2 100644
--- a/internal/cli/cmd/get/promotions.go
+++ b/internal/cli/cmd/get/promotions.go
@@ -19,14 +19,8 @@ import (
 	v1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type PromotionsFlags struct {
-	Stage option.Optional[string]
-}
-
 func newGetPromotionsCommand(opt *option.Option) *cobra.Command {
-	flag := PromotionsFlags{
-		Stage: option.OptionalString(),
-	}
+	var stage string
 	cmd := &cobra.Command{
 		Use:     "promotions --project=project [--stage=stage] [NAME...]",
 		Aliases: []string{"promotion", "promos", "promo"},
@@ -54,7 +48,7 @@ kargo get promotions --project=my-project some-promotion
 			req := &v1alpha1.ListPromotionsRequest{
 				Project: project,
 			}
-			if stage, ok := flag.Stage.Get(); ok {
+			if stage != "" {
 				req.Stage = proto.String(stage)
 			}
 
@@ -93,8 +87,8 @@ kargo get promotions --project=my-project some-promotion
 			return resErr
 		},
 	}
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
-	option.OptionalStage(flag.Stage)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
+	option.Stage(cmd.Flags(), &stage)
 	opt.PrintFlags.AddFlags(cmd)
 	return cmd
 }
diff --git a/internal/cli/cmd/get/stages.go b/internal/cli/cmd/get/stages.go
index be7d8aa0..ecedd4c7 100644
--- a/internal/cli/cmd/get/stages.go
+++ b/internal/cli/cmd/get/stages.go
@@ -78,7 +78,7 @@ kargo get stages --project=my-project my-stage
 			return resErr
 		},
 	}
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	opt.PrintFlags.AddFlags(cmd)
 	return cmd
 }
diff --git a/internal/cli/cmd/get/warehouse.go b/internal/cli/cmd/get/warehouse.go
index fb9e268e..cb22047c 100644
--- a/internal/cli/cmd/get/warehouse.go
+++ b/internal/cli/cmd/get/warehouse.go
@@ -83,7 +83,7 @@ kargo get warehouses --project=my-project my-warehouse
 			return resErr
 		},
 	}
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	opt.PrintFlags.AddFlags(cmd)
 	return cmd
 }
diff --git a/internal/cli/cmd/login/login.go b/internal/cli/cmd/login/login.go
index b08c3431..98ff6ec2 100644
--- a/internal/cli/cmd/login/login.go
+++ b/internal/cli/cmd/login/login.go
@@ -31,19 +31,15 @@ import (
 	v1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-const (
-	flagAdmin                = "admin"
-	flagKubeconfig           = "kubeconfig"
-	flagPassword             = "password"
-	flagPort                 = "port"
-	flagSSO                  = "sso"
-	defaultRandStringCharSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-)
+const defaultRandStringCharSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
 //go:embed assets
 var assets embed.FS
 
 func NewCommand(opt *option.Option) *cobra.Command {
+	var useAdmin, useKubeconfig, useSSO bool
+	var password string
+	var callbackPort int
 	cmd := &cobra.Command{
 		Use:     "login server-address",
 		Args:    option.ExactArgs(1),
@@ -52,21 +48,6 @@ func NewCommand(opt *option.Option) *cobra.Command {
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 
-			useAdmin, err := cmd.Flags().GetBool(flagAdmin)
-			if err != nil {
-				return err
-			}
-
-			useKubeconfig, err := cmd.Flags().GetBool(flagKubeconfig)
-			if err != nil {
-				return err
-			}
-
-			useSSO, err := cmd.Flags().GetBool(flagSSO)
-			if err != nil {
-				return err
-			}
-
 			var flagCount int
 			if useAdmin {
 				flagCount++
@@ -85,12 +66,8 @@ func NewCommand(opt *option.Option) *cobra.Command {
 
 			serverAddress := args[0]
 			var bearerToken, refreshToken string
+			var err error
 			if useAdmin {
-				var password string
-				if password, err = cmd.Flags().GetString(flagPassword); err != nil {
-					return err
-				}
-
 				for {
 					if password != "" {
 						break
@@ -102,23 +79,15 @@ func NewCommand(opt *option.Option) *cobra.Command {
 						return err
 					}
 				}
-
 				if bearerToken, err =
 					adminLogin(ctx, serverAddress, password, opt.InsecureTLS); err != nil {
 					return err
 				}
 			} else if useKubeconfig {
-				bearerToken, err = kubeconfigLogin(ctx)
-				if err != nil {
+				if bearerToken, err = kubeconfigLogin(ctx); err != nil {
 					return err
 				}
 			} else {
-				var callbackPort int
-				callbackPort, err = cmd.Flags().GetInt(flagPort)
-				if err != nil {
-					return err
-				}
-
 				if bearerToken, refreshToken, err =
 					ssoLogin(ctx, serverAddress, callbackPort, opt.InsecureTLS); err != nil {
 					return err
@@ -145,36 +114,41 @@ func NewCommand(opt *option.Option) *cobra.Command {
 			return errors.Wrap(err, "error persisting configuration")
 		},
 	}
-	cmd.Flags().BoolP(
-		flagAdmin,
+	cmd.Flags().BoolVarP(
+		&useAdmin,
+		"admin",
 		"a",
 		false,
 		"Log in as the Kargo admin user; mutually exclusive with --kubeconfig and "+
 			"--sso",
 	)
-	cmd.Flags().BoolP(
-		flagKubeconfig,
+	cmd.Flags().BoolVarP(
+		&useKubeconfig,
+		"kubeconfig",
 		"k",
 		false,
 		"Log in using a token obtained from the local Kubernetes configuration's "+
 			"current context; mutually exclusive with --admin and --sso",
 	)
-	cmd.Flags().StringP(
-		flagPassword,
+	cmd.Flags().StringVarP(
+		&password,
+		"password",
 		"P",
 		"",
 		"Specify the password for non-interactive admin user login; only used "+
 			"with --admin",
 	)
-	cmd.Flags().IntP(
-		flagPort,
+	cmd.Flags().IntVarP(
+		&callbackPort,
+		"port",
 		"p",
 		0,
 		"Port to use for the callback URL; 0 selects any available, "+
 			"unprivileged port; only used with --sso",
 	)
-	cmd.Flags().BoolP(
-		flagSSO,
+	cmd.Flags().BoolVarP(
+		&useSSO,
+		"sso",
 		"s",
 		false,
 		"Log in using OpenID Connect and the server's configured identity "+
diff --git a/internal/cli/cmd/logout/logout.go b/internal/cli/cmd/logout/logout.go
new file mode 100644
index 00000000..e45cc490
--- /dev/null
+++ b/internal/cli/cmd/logout/logout.go
@@ -0,0 +1,22 @@
+package logout
+
+import (
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"github.com/akuity/kargo/internal/cli/config"
+)
+
+func NewCommand() *cobra.Command {
+	return &cobra.Command{
+		Use:     "logout",
+		Short:   "Log out of the Kargo API server",
+		Example: "kargo logout",
+		RunE: func(*cobra.Command, []string) error {
+			return errors.Wrap(
+				config.DeleteCLIConfig(),
+				"error deleting CLI configuration",
+			)
+		},
+	}
+}
diff --git a/internal/cli/cmd/refresh/refresh.go b/internal/cli/cmd/refresh/refresh.go
index c181d5ed..90ee2398 100644
--- a/internal/cli/cmd/refresh/refresh.go
+++ b/internal/cli/cmd/refresh/refresh.go
@@ -23,23 +23,15 @@ func NewCommand(opt *option.Option) *cobra.Command {
 	return cmd
 }
 
-type Flags struct {
-	Wait bool
-}
-
 const (
 	refreshResourceTypeWarehouse = "warehouse"
 	refreshResourceTypeStage     = "stage"
 )
 
-func addRefreshFlags(cmd *cobra.Command, flag *Flags) {
-	cmd.Flags().BoolVar(&flag.Wait, "wait", true, "Wait until refresh completes")
-}
-
 func refreshObject(
 	opt *option.Option,
-	flag *Flags,
 	resourceType string,
+	wait bool,
 ) func(cmd *cobra.Command, args []string) error {
 	return func(cmd *cobra.Command, args []string) error {
 		ctx := cmd.Context()
@@ -74,7 +66,7 @@ func refreshObject(
 			return errors.Wrapf(err, "refresh %s", resourceType)
 		}
 
-		if flag.Wait {
+		if wait {
 			switch resourceType {
 			case refreshResourceTypeWarehouse:
 				err = waitForWarehouse(ctx, kargoSvcCli, project, name)
diff --git a/internal/cli/cmd/refresh/stage.go b/internal/cli/cmd/refresh/stage.go
index d7567956..4380f03d 100644
--- a/internal/cli/cmd/refresh/stage.go
+++ b/internal/cli/cmd/refresh/stage.go
@@ -14,15 +14,15 @@ import (
 )
 
 func newRefreshStageCommand(opt *option.Option) *cobra.Command {
-	var flag Flags
+	var wait bool
 	cmd := &cobra.Command{
 		Use:     "stage (STAGE)",
 		Args:    option.ExactArgs(1),
 		Example: "kargo refresh stage --project=guestbook (STAGE)",
-		RunE:    refreshObject(opt, &flag, "stage"),
+		RunE:    refreshObject(opt, "stage", wait),
 	}
-	addRefreshFlags(cmd, &flag)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Wait(cmd.Flags(), &wait)
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
 
diff --git a/internal/cli/cmd/refresh/warehouse.go b/internal/cli/cmd/refresh/warehouse.go
index 5411ea44..ccd4cedf 100644
--- a/internal/cli/cmd/refresh/warehouse.go
+++ b/internal/cli/cmd/refresh/warehouse.go
@@ -14,15 +14,15 @@ import (
 )
 
 func newRefreshWarehouseCommand(opt *option.Option) *cobra.Command {
-	var flag Flags
+	var wait bool
 	cmd := &cobra.Command{
 		Use:     "warehouse (WAREHOUSE)",
 		Args:    option.ExactArgs(1),
 		Example: "kargo warehouse refresh --project=guestbook (WAREHOUSE)",
-		RunE:    refreshObject(opt, &flag, "warehouse"),
+		RunE:    refreshObject(opt, "warehouse", wait),
 	}
-	addRefreshFlags(cmd, &flag)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Wait(cmd.Flags(), &wait)
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
 
diff --git a/internal/cli/cmd/stage/promote.go b/internal/cli/cmd/stage/promote.go
index 666a4f3f..be0e9728 100644
--- a/internal/cli/cmd/stage/promote.go
+++ b/internal/cli/cmd/stage/promote.go
@@ -15,12 +15,8 @@ import (
 	v1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type PromoteFlags struct {
-	Freight string
-}
-
 func newPromoteCommand(opt *option.Option) *cobra.Command {
-	var flag PromoteFlags
+	var freight string
 	cmd := &cobra.Command{
 		Use:  "promote --project=project (STAGE) [(--freight=)freight-id]",
 		Args: option.ExactArgs(1),
@@ -50,7 +46,6 @@ kargo stage promote dev --freight=abc123
 				return errors.New("name is required")
 			}
 
-			freight := strings.TrimSpace(flag.Freight)
 			if freight == "" {
 				// TODO: Get latest available freight if empty
 				return errors.New("freight is required")
@@ -79,7 +74,7 @@ kargo stage promote dev --freight=abc123
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Freight(&flag.Freight)(cmd.Flags())
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Freight(cmd.Flags(), &freight)
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/cmd/stage/promote_subscribers.go b/internal/cli/cmd/stage/promote_subscribers.go
index 93e987c2..541c06ce 100644
--- a/internal/cli/cmd/stage/promote_subscribers.go
+++ b/internal/cli/cmd/stage/promote_subscribers.go
@@ -15,12 +15,8 @@ import (
 	v1alpha1 "github.com/akuity/kargo/pkg/api/service/v1alpha1"
 )
 
-type PromoteSubscribersFlags struct {
-	Freight string
-}
-
 func newPromoteSubscribersCommand(opt *option.Option) *cobra.Command {
-	var flag PromoteSubscribersFlags
+	var freight string
 	cmd := &cobra.Command{
 		Use:  "promote-subscribers --project=project (STAGE) [(--freight=)freight-id]",
 		Args: option.ExactArgs(1),
@@ -50,7 +46,6 @@ kargo stage promote-subscribers dev --freight=abc123
 				return errors.New("name is required")
 			}
 
-			freight := strings.TrimSpace(flag.Freight)
 			if freight == "" {
 				return errors.New("freight is required")
 			}
@@ -84,7 +79,7 @@ kargo stage promote-subscribers dev --freight=abc123
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Freight(&flag.Freight)(cmd.Flags())
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Freight(cmd.Flags(), &freight)
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/cmd/stage/set_auto_promotion.go b/internal/cli/cmd/stage/set_auto_promotion.go
index 396f3f0c..a3b39189 100644
--- a/internal/cli/cmd/stage/set_auto_promotion.go
+++ b/internal/cli/cmd/stage/set_auto_promotion.go
@@ -44,7 +44,7 @@ kargo stage enable-auto-promotion dev
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
 
@@ -76,7 +76,7 @@ kargo stage disable-auto-promotion dev
 		},
 	}
 	opt.PrintFlags.AddFlags(cmd)
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
 
diff --git a/internal/cli/cmd/update/freight-alias.go b/internal/cli/cmd/update/freight-alias.go
index 8ee5249a..260d3afc 100644
--- a/internal/cli/cmd/update/freight-alias.go
+++ b/internal/cli/cmd/update/freight-alias.go
@@ -45,6 +45,6 @@ func newUpdateFreightAliasCommand(opt *option.Option) *cobra.Command {
 			return nil
 		},
 	}
-	option.Project(&opt.Project, opt.Project)(cmd.Flags())
+	option.Project(cmd.Flags(), opt, opt.Project)
 	return cmd
 }
diff --git a/internal/cli/option/flag.go b/internal/cli/option/flag.go
index 28aa9242..be02e577 100644
--- a/internal/cli/option/flag.go
+++ b/internal/cli/option/flag.go
@@ -6,41 +6,31 @@ import (
 	"github.com/spf13/pflag"
 )
 
-type FlagFn func(*pflag.FlagSet)
+func Filenames(fs *pflag.FlagSet, filenames *[]string, verb string) {
+	fs.StringSliceVarP(filenames, "filename", "f", nil,
+		fmt.Sprintf("Filename, directory, or URL to files to use to %s the resource", verb))
+}
 
-func Filenames(verb string, v *[]string) FlagFn {
-	return func(fs *pflag.FlagSet) {
-		fs.StringSliceVarP(v, "filename", "f", nil,
-			fmt.Sprintf("Filename, directory, or URL to files to use to %s the resource", verb))
-	}
+func InsecureTLS(fs *pflag.FlagSet, opt *Option) {
+	fs.BoolVar(&opt.InsecureTLS, "insecure-skip-tls-verify", false, "Skip TLS certificate verification")
 }
 
-func InsecureTLS(v *bool) FlagFn {
-	return func(fs *pflag.FlagSet) {
-		fs.BoolVar(v, "insecure-skip-tls-verify", false, "Skip TLS certificate verification")
-	}
+func LocalServer(fs *pflag.FlagSet, opt *Option) {
+	fs.BoolVar(&opt.UseLocalServer, "local-server", false, "Use local server")
 }
 
-func LocalServer(v *bool) FlagFn {
-	return func(fs *pflag.FlagSet) {
-		fs.BoolVar(v, "local-server", false, "Use local server")
-	}
+func Project(fs *pflag.FlagSet, opt *Option, defaultProject string) {
+	fs.StringVarP(&opt.Project, "project", "p", defaultProject, "Project")
 }
 
-func Project(v *string, defaultProject string) FlagFn {
-	return func(s *pflag.FlagSet) {
-		s.StringVarP(v, "project", "p", defaultProject, "Project")
-	}
+func Stage(fs *pflag.FlagSet, stage *string) {
+	fs.StringVar(stage, "stage", "", "Stage")
 }
 
-func OptionalStage(v Optional[string]) FlagFn {
-	return func(fs *pflag.FlagSet) {
-		fs.Var(v, "stage", "Stage")
-	}
+func Freight(fs *pflag.FlagSet, freight *string) {
+	fs.StringVar(freight, "freight", "", "Freight ID")
 }
 
-func Freight(v *string) FlagFn {
-	return func(fs *pflag.FlagSet) {
-		fs.StringVar(v, "freight", "", "Freight ID")
-	}
+func Wait(fs *pflag.FlagSet, wait *bool) {
+	fs.BoolVar(wait, "wait", false, "Wait until refresh completes")
 }
diff --git a/internal/cli/option/types.go b/internal/cli/option/types.go
deleted file mode 100644
index 8989c459..00000000
--- a/internal/cli/option/types.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package option
-
-import (
-	"strconv"
-
-	"github.com/pkg/errors"
-	"github.com/samber/mo"
-	"github.com/spf13/pflag"
-)
-
-type Optional[T any] interface {
-	pflag.Value
-
-	/* partial methods of mo.Option */
-
-	IsPresent() bool
-	Get() (T, bool)
-	OrElse(fallback T) T
-}
-
-type optionalBool struct {
-	mo.Option[bool]
-}
-
-func (o *optionalBool) String() string {
-	v, ok := o.Get()
-	if !ok {
-		return ""
-	}
-	return strconv.FormatBool(v)
-}
-
-func (o *optionalBool) Set(value string) error {
-	v, err := strconv.ParseBool(value)
-	if err != nil {
-		return errors.Wrap(err, "parse bool")
-	}
-	o.Option = mo.Some[bool](v)
-	return nil
-}
-
-func (o *optionalBool) Type() string {
-	return "bool"
-}
-
-func OptionalBool() Optional[bool] {
-	return &optionalBool{
-		Option: mo.None[bool](),
-	}
-}
-
-type optionalString struct {
-	mo.Option[string]
-}
-
-func (o *optionalString) String() string {
-	v, ok := o.Get()
-	if !ok {
-		return ""
-	}
-	return v
-}
-
-func (o *optionalString) Set(value string) error {
-	o.Option = mo.Some[string](value)
-	return nil
-}
-
-func (o *optionalString) Type() string {
-	return "string"
-}
-
-func OptionalString() Optional[string] {
-	return &optionalString{
-		Option: mo.None[string](),
-	}
-}
diff --git a/internal/controller/git/git.go b/internal/controller/git/git.go
index 9fc96d1b..f0394509 100644
--- a/internal/controller/git/git.go
+++ b/internal/controller/git/git.go
@@ -97,10 +97,11 @@ type Repo interface {
 // repo is an implementation of the Repo interface for interacting with a git
 // repository.
 type repo struct {
-	url           string
-	homeDir       string
-	dir           string
-	currentBranch string
+	url                   string
+	homeDir               string
+	dir                   string
+	currentBranch         string
+	insecureSkipTLSVerify bool
 }
 
 // CloneOptions represents options for cloning a git repository.
@@ -114,6 +115,10 @@ type CloneOptions struct {
 	// useful for speeding up the cloning process when all we care about is the
 	// latest commit from a single branch.
 	Shallow bool
+	// InsecureSkipTLSVerify specifies whether certificate verification errors
+	// should be ignored when cloning the repository. The setting will be
+	// remembered for subsequent interactions with the remote repository.
+	InsecureSkipTLSVerify bool
 }
 
 // Clone produces a local clone of the remote git repository at the specified
@@ -135,9 +140,10 @@ func Clone(
 		)
 	}
 	r := &repo{
-		url:     repoURL,
-		homeDir: homeDir,
-		dir:     filepath.Join(homeDir, "repo"),
+		url:                   repoURL,
+		homeDir:               homeDir,
+		dir:                   filepath.Join(homeDir, "repo"),
+		insecureSkipTLSVerify: opts.InsecureSkipTLSVerify,
 	}
 	if err = r.setupAuth(repoCreds); err != nil {
 		return nil, err
@@ -518,6 +524,9 @@ func (r *repo) buildCommand(arg ...string) *exec.Cmd {
 	} else {
 		cmd.Env = append(cmd.Env, homeEnvVar)
 	}
+	if r.insecureSkipTLSVerify {
+		cmd.Env = append(cmd.Env, "GIT_SSL_NO_VERIFY=true")
+	}
 	cmd.Dir = r.dir
 	return cmd
 }
diff --git a/internal/controller/management/projects/projects.go b/internal/controller/management/projects/projects.go
new file mode 100644
index 00000000..5a1a1c1e
--- /dev/null
+++ b/internal/controller/management/projects/projects.go
@@ -0,0 +1,41 @@
+package projects
+
+import (
+	"context"
+
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+
+	kargoapi "github.com/akuity/kargo/api/v1alpha1"
+	"github.com/akuity/kargo/internal/controller"
+)
+
+// reconciler reconciles Project resources.
+type reconciler struct {
+	kargoClient client.Client
+}
+
+// SetupReconcilerWithManager initializes a reconciler for Project resources and
+// registers it with the provided Manager.
+func SetupReconcilerWithManager(kargoMgr manager.Manager) error {
+	return ctrl.NewControllerManagedBy(kargoMgr).
+		For(&kargoapi.Project{}).
+		WithOptions(controller.CommonOptions()).
+		Complete(newReconciler(kargoMgr.GetClient()))
+}
+
+func newReconciler(kargoClient client.Client) *reconciler {
+	return &reconciler{
+		kargoClient: kargoClient,
+	}
+}
+
+// Reconcile is part of the main Kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+func (r *reconciler) Reconcile(
+	context.Context,
+	ctrl.Request,
+) (ctrl.Result, error) {
+	return ctrl.Result{}, nil
+}
diff --git a/internal/controller/promotion/argocd.go b/internal/controller/promotion/argocd.go
index 590a2c8a..c63a539f 100644
--- a/internal/controller/promotion/argocd.go
+++ b/internal/controller/promotion/argocd.go
@@ -25,7 +25,7 @@ type argoCDMechanism struct {
 		ctx context.Context,
 		stageMeta metav1.ObjectMeta,
 		update kargoapi.ArgoCDAppUpdate,
-		newFreight kargoapi.SimpleFreight,
+		newFreight kargoapi.FreightReference,
 	) error
 	getArgoCDAppFn func(
 		ctx context.Context,
@@ -34,7 +34,7 @@ type argoCDMechanism struct {
 	) (*argocd.Application, error)
 	applyArgoCDSourceUpdateFn func(
 		argocd.ApplicationSource,
-		kargoapi.SimpleFreight,
+		kargoapi.FreightReference,
 		kargoapi.ArgoCDSourceUpdate,
 	) (argocd.ApplicationSource, error)
 	argoCDAppPatchFn func(
@@ -68,8 +68,8 @@ func (a *argoCDMechanism) Promote(
 	ctx context.Context,
 	stage *kargoapi.Stage,
 	promo *kargoapi.Promotion,
-	newFreight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	updates := stage.Spec.PromotionMechanisms.ArgoCDAppUpdates
 
 	if len(updates) == 0 {
@@ -99,7 +99,7 @@ func (a *argoCDMechanism) doSingleUpdate(
 	ctx context.Context,
 	stageMeta metav1.ObjectMeta,
 	update kargoapi.ArgoCDAppUpdate,
-	newFreight kargoapi.SimpleFreight,
+	newFreight kargoapi.FreightReference,
 ) error {
 	app, err :=
 		a.getArgoCDAppFn(ctx, update.AppNamespaceOrDefault(), update.AppName)
@@ -278,7 +278,7 @@ func authorizeArgoCDAppUpdate(
 // applyArgoCDSourceUpdate updates a single Argo CD ApplicationSource.
 func applyArgoCDSourceUpdate(
 	source argocd.ApplicationSource,
-	newFreight kargoapi.SimpleFreight,
+	newFreight kargoapi.FreightReference,
 	update kargoapi.ArgoCDSourceUpdate,
 ) (argocd.ApplicationSource, error) {
 	if source.RepoURL != update.RepoURL || source.Chart != update.Chart {
diff --git a/internal/controller/promotion/composite.go b/internal/controller/promotion/composite.go
index 45b2f8d8..291bed1a 100644
--- a/internal/controller/promotion/composite.go
+++ b/internal/controller/promotion/composite.go
@@ -44,8 +44,8 @@ func (c *compositeMechanism) Promote(
 	ctx context.Context,
 	stage *kargoapi.Stage,
 	promo *kargoapi.Promotion,
-	newFreight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	if stage.Spec.PromotionMechanisms == nil {
 		return &kargoapi.PromotionStatus{Phase: kargoapi.PromotionPhaseSucceeded},
 			newFreight, nil
diff --git a/internal/controller/promotion/git.go b/internal/controller/promotion/git.go
index 7f76c6f8..3f0a784c 100644
--- a/internal/controller/promotion/git.go
+++ b/internal/controller/promotion/git.go
@@ -25,8 +25,8 @@ type gitMechanism struct {
 		ctx context.Context,
 		promo *kargoapi.Promotion,
 		update kargoapi.GitRepoUpdate,
-		newFreight kargoapi.SimpleFreight,
-	) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error)
+		newFreight kargoapi.FreightReference,
+	) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error)
 	getReadRefFn func(
 		update kargoapi.GitRepoUpdate,
 		commits []kargoapi.GitCommit,
@@ -38,14 +38,14 @@ type gitMechanism struct {
 	) (*git.RepoCredentials, error)
 	gitCommitFn func(
 		update kargoapi.GitRepoUpdate,
-		newFreight kargoapi.SimpleFreight,
+		newFreight kargoapi.FreightReference,
 		readRef string,
 		writeBranch string,
 		repo git.Repo,
 	) (string, error)
 	applyConfigManagementFn func(
 		update kargoapi.GitRepoUpdate,
-		newFreight kargoapi.SimpleFreight,
+		newFreight kargoapi.FreightReference,
 		homeDir string,
 		workingDir string,
 	) ([]string, error)
@@ -61,7 +61,7 @@ func newGitMechanism(
 	selectUpdatesFn func([]kargoapi.GitRepoUpdate) []kargoapi.GitRepoUpdate,
 	applyConfigManagementFn func(
 		update kargoapi.GitRepoUpdate,
-		newFreight kargoapi.SimpleFreight,
+		newFreight kargoapi.FreightReference,
 		homeDir string,
 		workingDir string,
 	) ([]string, error),
@@ -88,8 +88,8 @@ func (g *gitMechanism) Promote(
 	ctx context.Context,
 	stage *kargoapi.Stage,
 	promo *kargoapi.Promotion,
-	newFreight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	updates := g.selectUpdatesFn(stage.Spec.PromotionMechanisms.GitRepoUpdates)
 
 	if len(updates) == 0 {
@@ -129,8 +129,8 @@ func (g *gitMechanism) doSingleUpdate(
 	ctx context.Context,
 	promo *kargoapi.Promotion,
 	update kargoapi.GitRepoUpdate,
-	newFreight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	readRef, commitIndex, err := g.getReadRefFn(update, newFreight.Commits)
 	if err != nil {
 		return nil, newFreight, err
@@ -147,7 +147,13 @@ func (g *gitMechanism) doSingleUpdate(
 	if creds == nil {
 		creds = &git.RepoCredentials{}
 	}
-	repo, err := git.Clone(update.RepoURL, *creds, nil)
+	repo, err := git.Clone(
+		update.RepoURL,
+		*creds,
+		&git.CloneOptions{
+			InsecureSkipTLSVerify: update.InsecureSkipTLSVerify,
+		},
+	)
 	if err != nil {
 		return nil, newFreight, errors.Wrapf(err, "error cloning git repo %q", update.RepoURL)
 	}
@@ -278,7 +284,7 @@ func getRepoCredentialsFn(
 // the above fails.
 func (g *gitMechanism) gitCommit(
 	update kargoapi.GitRepoUpdate,
-	newFreight kargoapi.SimpleFreight,
+	newFreight kargoapi.FreightReference,
 	readRef string,
 	writeBranch string,
 	repo git.Repo,
diff --git a/internal/controller/promotion/helm.go b/internal/controller/promotion/helm.go
index 509578e9..38265a16 100644
--- a/internal/controller/promotion/helm.go
+++ b/internal/controller/promotion/helm.go
@@ -63,7 +63,7 @@ type helmer struct {
 // directory.
 func (h *helmer) apply(
 	update kargoapi.GitRepoUpdate,
-	newFreight kargoapi.SimpleFreight,
+	newFreight kargoapi.FreightReference,
 	homeDir string,
 	workingDir string,
 ) ([]string, error) {
diff --git a/internal/controller/promotion/kustomize.go b/internal/controller/promotion/kustomize.go
index 071ca509..c5186ac0 100644
--- a/internal/controller/promotion/kustomize.go
+++ b/internal/controller/promotion/kustomize.go
@@ -48,7 +48,7 @@ type kustomizer struct {
 // working directory.
 func (k *kustomizer) apply(
 	update kargoapi.GitRepoUpdate,
-	newFreight kargoapi.SimpleFreight,
+	newFreight kargoapi.FreightReference,
 	_ string,
 	workingDir string,
 ) ([]string, error) {
diff --git a/internal/controller/promotion/render.go b/internal/controller/promotion/render.go
index 4eb9b6e5..0607affc 100644
--- a/internal/controller/promotion/render.go
+++ b/internal/controller/promotion/render.go
@@ -21,8 +21,8 @@ type kargoRenderMechanism struct {
 		ctx context.Context,
 		promo *kargoapi.Promotion,
 		update kargoapi.GitRepoUpdate,
-		newFreight kargoapi.SimpleFreight,
-	) (kargoapi.SimpleFreight, error)
+		newFreight kargoapi.FreightReference,
+	) (kargoapi.FreightReference, error)
 	getReadRefFn func(
 		update kargoapi.GitRepoUpdate,
 		commits []kargoapi.GitCommit,
@@ -60,8 +60,8 @@ func (b *kargoRenderMechanism) Promote(
 	ctx context.Context,
 	stage *kargoapi.Stage,
 	promo *kargoapi.Promotion,
-	newFreight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	updates := make([]kargoapi.GitRepoUpdate, 0, len(stage.Spec.PromotionMechanisms.GitRepoUpdates))
 	for _, update := range stage.Spec.PromotionMechanisms.GitRepoUpdates {
 		if update.Render != nil {
@@ -101,8 +101,8 @@ func (b *kargoRenderMechanism) doSingleUpdate(
 	ctx context.Context,
 	promo *kargoapi.Promotion,
 	update kargoapi.GitRepoUpdate,
-	newFreight kargoapi.SimpleFreight,
-) (kargoapi.SimpleFreight, error) {
+	newFreight kargoapi.FreightReference,
+) (kargoapi.FreightReference, error) {
 	logger := logging.LoggerFromContext(ctx).WithField("repo", update.RepoURL)
 
 	readRef, commitIndex, err := b.getReadRefFn(update, newFreight.Commits)
diff --git a/internal/controller/promotion/root.go b/internal/controller/promotion/root.go
index 0def8615..d49591e1 100644
--- a/internal/controller/promotion/root.go
+++ b/internal/controller/promotion/root.go
@@ -20,8 +20,8 @@ type Mechanism interface {
 		context.Context,
 		*kargoapi.Stage,
 		*kargoapi.Promotion,
-		kargoapi.SimpleFreight,
-	) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error)
+		kargoapi.FreightReference,
+	) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error)
 }
 
 // NewMechanisms returns the entrypoint to a hierarchical tree of promotion
diff --git a/internal/controller/promotions/promotions.go b/internal/controller/promotions/promotions.go
index 9edc8fa9..57bd68de 100644
--- a/internal/controller/promotions/promotions.go
+++ b/internal/controller/promotions/promotions.go
@@ -331,7 +331,7 @@ func (r *reconciler) promote(
 		)
 	}
 
-	simpleTargetFreight := kargoapi.SimpleFreight{
+	simpleTargetFreight := kargoapi.FreightReference{
 		ID:      targetFreight.ID,
 		Commits: targetFreight.Commits,
 		Images:  targetFreight.Images,
diff --git a/internal/controller/stages/health.go b/internal/controller/stages/health.go
index 45d14418..3aa407bf 100644
--- a/internal/controller/stages/health.go
+++ b/internal/controller/stages/health.go
@@ -10,7 +10,7 @@ import (
 
 func (r *reconciler) checkHealth(
 	ctx context.Context,
-	currentFreight kargoapi.SimpleFreight,
+	currentFreight kargoapi.FreightReference,
 	argoCDAppUpdates []kargoapi.ArgoCDAppUpdate,
 ) *kargoapi.Health {
 	if len(argoCDAppUpdates) == 0 {
diff --git a/internal/controller/stages/stages.go b/internal/controller/stages/stages.go
index f30c0844..42963b1e 100644
--- a/internal/controller/stages/stages.go
+++ b/internal/controller/stages/stages.go
@@ -54,7 +54,7 @@ type reconciler struct {
 
 	checkHealthFn func(
 		context.Context,
-		kargoapi.SimpleFreight,
+		kargoapi.FreightReference,
 		[]kargoapi.ArgoCDAppUpdate,
 	) *kargoapi.Health
 
diff --git a/internal/controller/warehouses/git.go b/internal/controller/warehouses/git.go
index 26d9b48d..fffc2e69 100644
--- a/internal/controller/warehouses/git.go
+++ b/internal/controller/warehouses/git.go
@@ -51,7 +51,7 @@ func (r *reconciler) getLatestCommits(
 			logger.Debug("found no credentials for git repo")
 		}
 
-		gm, err := r.getLatestCommitMetaFn(ctx, sub.RepoURL, sub.Branch, repoCreds)
+		gm, err := r.getLatestCommitMetaFn(ctx, *s.Git, repoCreds)
 		if err != nil {
 			return nil, errors.Wrapf(
 				err,
@@ -76,25 +76,25 @@ func (r *reconciler) getLatestCommits(
 
 func getLatestCommitMeta(
 	ctx context.Context,
-	repoURL string,
-	branch string,
+	sub kargoapi.GitSubscription,
 	creds *git.RepoCredentials,
 ) (*gitMeta, error) {
-	logger := logging.LoggerFromContext(ctx).WithField("repo", repoURL)
+	logger := logging.LoggerFromContext(ctx).WithField("repo", sub.RepoURL)
 	if creds == nil {
 		creds = &git.RepoCredentials{}
 	}
 	repo, err := git.Clone(
-		repoURL,
+		sub.RepoURL,
 		*creds,
 		&git.CloneOptions{
-			Branch:       branch,
-			SingleBranch: true,
-			Shallow:      true,
+			Branch:                sub.Branch,
+			SingleBranch:          true,
+			Shallow:               true,
+			InsecureSkipTLSVerify: sub.InsecureSkipTLSVerify,
 		},
 	)
 	if err != nil {
-		return nil, errors.Wrapf(err, "error cloning git repo %q", repoURL)
+		return nil, errors.Wrapf(err, "error cloning git repo %q", sub.RepoURL)
 	}
 	var gm gitMeta
 	gm.Commit, err = repo.LastCommitID()
@@ -102,8 +102,8 @@ func getLatestCommitMeta(
 		return nil, errors.Wrapf(
 			err,
 			"error determining last commit ID from git repo %q (branch: %q)",
-			repoURL,
-			branch,
+			sub.RepoURL,
+			sub.Branch,
 		)
 	}
 	msg, err := repo.CommitMessage(gm.Commit)
diff --git a/internal/controller/warehouses/warehouses.go b/internal/controller/warehouses/warehouses.go
index 2b6075b9..b86ce576 100644
--- a/internal/controller/warehouses/warehouses.go
+++ b/internal/controller/warehouses/warehouses.go
@@ -78,10 +78,9 @@ type reconciler struct {
 	) (string, error)
 
 	getLatestCommitMetaFn func(
-		ctx context.Context,
-		repoURL string,
-		branch string,
-		creds *git.RepoCredentials,
+		context.Context,
+		kargoapi.GitSubscription,
+		*git.RepoCredentials,
 	) (*gitMeta, error)
 
 	getAvailableFreightAliasFn func(context.Context) (string, error)
diff --git a/internal/credentials/credentials.go b/internal/credentials/credentials.go
index a4772c9a..8690fd28 100644
--- a/internal/credentials/credentials.go
+++ b/internal/credentials/credentials.go
@@ -78,8 +78,8 @@ type kubernetesDatabase struct {
 // kubernetesDatabase struct. It uses the functional options pattern to allow
 // for easy configuration.
 type kubernetesDatabaseConfig struct {
-	argoCDNamespace             string   `envconfig:"ARGOCD_NAMESPACE" default:"argocd"`
-	globalCredentialsNamespaces []string `envconfig:"GLOBAL_CREDENTIALS_NAMESPACES" default:""`
+	ArgoCDNamespace             string   `envconfig:"ARGOCD_NAMESPACE" default:"argocd"`
+	GlobalCredentialsNamespaces []string `envconfig:"GLOBAL_CREDENTIALS_NAMESPACES" default:""`
 
 	kargoClient client.Client
 	// it set we know that "borrowing ArgoCD creds" is enabled
@@ -172,7 +172,7 @@ func (k *kubernetesDatabase) Get(
 	}
 
 	// Check global credentials namespaces for credentials
-	for _, globalCredsNamespace := range k.globalCredentialsNamespaces {
+	for _, globalCredsNamespace := range k.GlobalCredentialsNamespaces {
 		// Check shared creds namespace for credentials
 		if secret, err = getCredentialsSecret(
 			ctx,
@@ -221,7 +221,7 @@ func (k *kubernetesDatabase) Get(
 	if secret, err = getCredentialsSecret(
 		ctx,
 		k.argoClient,
-		k.argoCDNamespace,
+		k.ArgoCDNamespace,
 		labels.Set(map[string]string{
 			argoCDSecretTypeLabelKey: repositorySecretTypeLabelValue,
 		}).AsSelector(),
@@ -237,7 +237,7 @@ func (k *kubernetesDatabase) Get(
 		if secret, err = getCredentialsSecret(
 			ctx,
 			k.argoClient,
-			k.argoCDNamespace,
+			k.ArgoCDNamespace,
 			labels.Set(map[string]string{
 				argoCDSecretTypeLabelKey: repoCredsSecretTypeLabelValue,
 			}).AsSelector(),
diff --git a/internal/credentials/credentials_options.go b/internal/credentials/credentials_options.go
index a27e698d..9ff2f98d 100644
--- a/internal/credentials/credentials_options.go
+++ b/internal/credentials/credentials_options.go
@@ -4,13 +4,13 @@ import "sigs.k8s.io/controller-runtime/pkg/client"
 
 func WithArgoCDNamespace(namespace string) KubernetesDatabaseOption {
 	return func(config *kubernetesDatabaseConfig) {
-		config.argoCDNamespace = namespace
+		config.ArgoCDNamespace = namespace
 	}
 }
 
 func WithGlobalCredentialsNamespaces(namespaces []string) KubernetesDatabaseOption {
 	return func(config *kubernetesDatabaseConfig) {
-		config.globalCredentialsNamespaces = namespaces
+		config.GlobalCredentialsNamespaces = namespaces
 	}
 }
 
diff --git a/pkg/api/v1alpha1/types.pb.go b/pkg/api/v1alpha1/types.pb.go
index 61ec722a..42f150b1 100644
--- a/pkg/api/v1alpha1/types.pb.go
+++ b/pkg/api/v1alpha1/types.pb.go
@@ -1647,8 +1647,8 @@ type PromotionInfo struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Name    string         `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	Freight *SimpleFreight `protobuf:"bytes,2,opt,name=freight,proto3" json:"freight,omitempty"`
+	Name    string            `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Freight *FreightReference `protobuf:"bytes,2,opt,name=freight,proto3" json:"freight,omitempty"`
 }
 
 func (x *PromotionInfo) Reset() {
@@ -1690,7 +1690,7 @@ func (x *PromotionInfo) GetName() string {
 	return ""
 }
 
-func (x *PromotionInfo) GetFreight() *SimpleFreight {
+func (x *PromotionInfo) GetFreight() *FreightReference {
 	if x != nil {
 		return x.Freight
 	}
@@ -2553,7 +2553,7 @@ func (*ApprovedStage) Descriptor() ([]byte, []int) {
 	return file_v1alpha1_types_proto_rawDescGZIP(), []int{40}
 }
 
-type SimpleFreight struct {
+type FreightReference struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
@@ -2566,8 +2566,8 @@ type SimpleFreight struct {
 	VerificationInfo *VerificationInfo      `protobuf:"bytes,7,opt,name=verification_info,json=verificationInfo,proto3,oneof" json:"verification_info,omitempty"`
 }
 
-func (x *SimpleFreight) Reset() {
-	*x = SimpleFreight{}
+func (x *FreightReference) Reset() {
+	*x = FreightReference{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_v1alpha1_types_proto_msgTypes[41]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -2575,13 +2575,13 @@ func (x *SimpleFreight) Reset() {
 	}
 }
 
-func (x *SimpleFreight) String() string {
+func (x *FreightReference) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SimpleFreight) ProtoMessage() {}
+func (*FreightReference) ProtoMessage() {}
 
-func (x *SimpleFreight) ProtoReflect() protoreflect.Message {
+func (x *FreightReference) ProtoReflect() protoreflect.Message {
 	mi := &file_v1alpha1_types_proto_msgTypes[41]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -2593,47 +2593,47 @@ func (x *SimpleFreight) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SimpleFreight.ProtoReflect.Descriptor instead.
-func (*SimpleFreight) Descriptor() ([]byte, []int) {
+// Deprecated: Use FreightReference.ProtoReflect.Descriptor instead.
+func (*FreightReference) Descriptor() ([]byte, []int) {
 	return file_v1alpha1_types_proto_rawDescGZIP(), []int{41}
 }
 
-func (x *SimpleFreight) GetId() string {
+func (x *FreightReference) GetId() string {
 	if x != nil {
 		return x.Id
 	}
 	return ""
 }
 
-func (x *SimpleFreight) GetFirstSeen() *timestamppb.Timestamp {
+func (x *FreightReference) GetFirstSeen() *timestamppb.Timestamp {
 	if x != nil {
 		return x.FirstSeen
 	}
 	return nil
 }
 
-func (x *SimpleFreight) GetCommits() []*GitCommit {
+func (x *FreightReference) GetCommits() []*GitCommit {
 	if x != nil {
 		return x.Commits
 	}
 	return nil
 }
 
-func (x *SimpleFreight) GetImages() []*Image {
+func (x *FreightReference) GetImages() []*Image {
 	if x != nil {
 		return x.Images
 	}
 	return nil
 }
 
-func (x *SimpleFreight) GetCharts() []*Chart {
+func (x *FreightReference) GetCharts() []*Chart {
 	if x != nil {
 		return x.Charts
 	}
 	return nil
 }
 
-func (x *SimpleFreight) GetVerificationInfo() *VerificationInfo {
+func (x *FreightReference) GetVerificationInfo() *VerificationInfo {
 	if x != nil {
 		return x.VerificationInfo
 	}
@@ -2645,12 +2645,12 @@ type StageStatus struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	CurrentFreight   *SimpleFreight   `protobuf:"bytes,2,opt,name=current_freight,json=currentFreight,proto3,oneof" json:"current_freight,omitempty"`
-	History          []*SimpleFreight `protobuf:"bytes,3,rep,name=history,proto3" json:"history,omitempty"`
-	Error            string           `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
-	Health           *Health          `protobuf:"bytes,5,opt,name=health,proto3,oneof" json:"health,omitempty"`
-	CurrentPromotion *PromotionInfo   `protobuf:"bytes,6,opt,name=current_promotion,json=currentPromotion,proto3,oneof" json:"current_promotion,omitempty"`
-	Phase            string           `protobuf:"bytes,7,opt,name=phase,proto3" json:"phase,omitempty"`
+	CurrentFreight   *FreightReference   `protobuf:"bytes,2,opt,name=current_freight,json=currentFreight,proto3,oneof" json:"current_freight,omitempty"`
+	History          []*FreightReference `protobuf:"bytes,3,rep,name=history,proto3" json:"history,omitempty"`
+	Error            string              `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
+	Health           *Health             `protobuf:"bytes,5,opt,name=health,proto3,oneof" json:"health,omitempty"`
+	CurrentPromotion *PromotionInfo      `protobuf:"bytes,6,opt,name=current_promotion,json=currentPromotion,proto3,oneof" json:"current_promotion,omitempty"`
+	Phase            string              `protobuf:"bytes,7,opt,name=phase,proto3" json:"phase,omitempty"`
 }
 
 func (x *StageStatus) Reset() {
@@ -2685,14 +2685,14 @@ func (*StageStatus) Descriptor() ([]byte, []int) {
 	return file_v1alpha1_types_proto_rawDescGZIP(), []int{42}
 }
 
-func (x *StageStatus) GetCurrentFreight() *SimpleFreight {
+func (x *StageStatus) GetCurrentFreight() *FreightReference {
 	if x != nil {
 		return x.CurrentFreight
 	}
 	return nil
 }
 
-func (x *StageStatus) GetHistory() []*SimpleFreight {
+func (x *StageStatus) GetHistory() []*FreightReference {
 	if x != nil {
 		return x.History
 	}
@@ -3614,105 +3614,155 @@ var file_v1alpha1_types_proto_rawDesc = []byte{
 	0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
 	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e,
 	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x0b,
-	0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x76, 0x0a, 0x0d, 0x50,
+	0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x79, 0x0a, 0x0d, 0x50,
 	0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04,
 	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x12, 0x51, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
+	0x12, 0x54, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
 	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x6d,
-	0x70, 0x6c, 0x65, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x07, 0x66, 0x72, 0x65, 0x69,
-	0x67, 0x68, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f,
-	0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x51, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31,
-	0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08, 0x6d, 0x65, 0x74,
-	0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x12, 0x49, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d,
-	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x69, 0x74,
-	0x65, 0x6d, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x22, 0xe1, 0x01, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65,
-	0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x12, 0x61, 0x0a, 0x10, 0x67, 0x69, 0x74, 0x5f,
-	0x72, 0x65, 0x70, 0x6f, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
+	0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65,
+	0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x66,
+	0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6d, 0x6f,
+	0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x51, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
+	0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74,
+	0x61, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x48, 0x00, 0x52, 0x08,
+	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x12, 0x49, 0x0a, 0x05, 0x69,
+	0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64,
+	0x61, 0x74, 0x61, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f,
+	0x6e, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x12, 0x61, 0x0a, 0x10, 0x67,
+	0x69, 0x74, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
+	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
+	0x2e, 0x47, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0e,
+	0x67, 0x69, 0x74, 0x52, 0x65, 0x70, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x67,
+	0x0a, 0x12, 0x61, 0x72, 0x67, 0x6f, 0x63, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x75, 0x70, 0x64,
+	0x61, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x6f, 0x43, 0x44, 0x41, 0x70, 0x70, 0x55,
+	0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x10, 0x61, 0x72, 0x67, 0x6f, 0x43, 0x44, 0x41, 0x70, 0x70,
+	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x6d,
+	0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x61,
+	0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04,
+	0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64,
+	0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x32, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
 	0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67,
-	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x69,
-	0x74, 0x52, 0x65, 0x70, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x67, 0x69, 0x74,
-	0x52, 0x65, 0x70, 0x6f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x12, 0x67, 0x0a, 0x12, 0x61,
-	0x72, 0x67, 0x6f, 0x63, 0x64, 0x5f, 0x61, 0x70, 0x70, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
-	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x41, 0x72, 0x67, 0x6f, 0x43, 0x44, 0x41, 0x70, 0x70, 0x55, 0x70, 0x64, 0x61,
-	0x74, 0x65, 0x52, 0x10, 0x61, 0x72, 0x67, 0x6f, 0x43, 0x44, 0x41, 0x70, 0x70, 0x55, 0x70, 0x64,
-	0x61, 0x74, 0x65, 0x73, 0x22, 0xe0, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69,
-	0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f,
-	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61,
-	0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e,
-	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x4e, 0x0a,
-	0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x32, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
+	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65,
+	0x63, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
+	0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74,
+	0x6f, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb4, 0x01, 0x0a, 0x13, 0x50,
+	0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4c, 0x69,
+	0x73, 0x74, 0x12, 0x4c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70,
+	0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4c, 0x69,
+	0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x12, 0x4f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x39, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
 	0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70,
-	0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d,
-	0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a,
-	0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74,
-	0x61, 0x67, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x75,
-	0x74, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x13, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x6f, 0x50, 0x72,
-	0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb4, 0x01, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x6d,
-	0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x12,
-	0x4c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
-	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d,
-	0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4f, 0x0a,
-	0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67,
+	0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f,
+	0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d,
+	0x73, 0x22, 0x3f, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70,
+	0x65, 0x63, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x69,
+	0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x72, 0x65, 0x69, 0x67,
+	0x68, 0x74, 0x22, 0xe3, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
+	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x63, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
+	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
+	0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
+	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb0, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x70,
+	0x6f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a,
+	0x03, 0x67, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x69, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x67, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12,
+	0x56, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b,
+	0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69,
+	0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69,
+	0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53,
+	0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x05, 0x69,
+	0x6d, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x12, 0x56, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
+	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x48, 0x02, 0x52, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x42,
+	0x06, 0x0a, 0x04, 0x5f, 0x67, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6d, 0x61, 0x67,
+	0x65, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x22, 0xa4, 0x02, 0x0a, 0x05,
+	0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x67,
 	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79,
-	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f,
-	0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x3f,
-	0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12,
-	0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
-	0x73, 0x74, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22,
-	0xe3, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73,
-	0x61, 0x67, 0x65, 0x12, 0x63, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18,
-	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
-	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08,
-	0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb0, 0x02, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x53, 0x75,
-	0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x03, 0x67, 0x69,
-	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x47, 0x69, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
-	0x6f, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x67, 0x69, 0x74, 0x88, 0x01, 0x01, 0x12, 0x56, 0x0a, 0x05,
-	0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e,
-	0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
-	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x73,
-	0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x01, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67,
-	0x65, 0x88, 0x01, 0x01, 0x12, 0x56, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d,
+	0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x61,
+	0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x04, 0x73, 0x70,
+	0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
+	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
+	0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73,
+	0x70, 0x65, 0x63, 0x12, 0x4d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
 	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
-	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43,
-	0x68, 0x61, 0x72, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x48, 0x02, 0x52, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74, 0x88, 0x01, 0x01, 0x42, 0x06, 0x0a, 0x04,
-	0x5f, 0x67, 0x69, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x42, 0x08,
-	0x0a, 0x06, 0x5f, 0x63, 0x68, 0x61, 0x72, 0x74, 0x22, 0xa4, 0x02, 0x0a, 0x05, 0x53, 0x74, 0x61,
-	0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53,
+	0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x22, 0xa0, 0x01, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74,
+	0x12, 0x4c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
+	0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67,
+	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
+	0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x45,
+	0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e,
+	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74,
+	0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e,
+	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05,
+	0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xce, 0x02, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53,
+	0x70, 0x65, 0x63, 0x12, 0x5d, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x73, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x12, 0x70, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+	0x6d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x3d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
+	0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61,
+	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d,
+	0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x52,
+	0x13, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e,
+	0x69, 0x73, 0x6d, 0x73, 0x12, 0x5f, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
+	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x48, 0x00, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69,
+	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xd0, 0x03, 0x0a, 0x07, 0x46, 0x72, 0x65, 0x69, 0x67,
+	0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
 	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73,
 	0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
 	0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
@@ -3720,297 +3770,248 @@ var file_v1alpha1_types_proto_rawDesc = []byte{
 	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61,
 	0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61,
 	0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d,
-	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x47, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
-	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63,
-	0x12, 0x4d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x35, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x69,
+	0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
+	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
+	0x68, 0x61, 0x31, 0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x07, 0x63,
+	0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x47, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73,
+	0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
+	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12,
+	0x47, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
+	0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70,
+	0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x74,
+	0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x12, 0x4f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
+	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
+	0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xd7, 0x03, 0x0a, 0x0d, 0x46, 0x72,
+	0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x68, 0x0a, 0x0b, 0x76,
+	0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x47, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
 	0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67,
-	0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
-	0xa0, 0x01, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x4c, 0x0a,
-	0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
+	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69,
+	0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69,
+	0x65, 0x64, 0x49, 0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x69, 0x66,
+	0x69, 0x65, 0x64, 0x49, 0x6e, 0x12, 0x6b, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65,
+	0x64, 0x5f, 0x66, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x48, 0x2e, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e,
+	0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
+	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x72,
+	0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x46,
+	0x6f, 0x72, 0x1a, 0x76, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x49, 0x6e,
+	0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
+	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x77, 0x0a, 0x10, 0x41, 0x70,
+	0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
+	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+	0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
 	0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70,
-	0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x74,
-	0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x45, 0x0a, 0x05, 0x69,
-	0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
-	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
-	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65,
-	0x6d, 0x73, 0x22, 0xce, 0x02, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x70, 0x65, 0x63,
-	0x12, 0x5d, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
+	0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f,
+	0x76, 0x65, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
+	0x02, 0x38, 0x01, 0x22, 0x0f, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x53,
+	0x74, 0x61, 0x67, 0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64,
+	0x53, 0x74, 0x61, 0x67, 0x65, 0x22, 0xd6, 0x03, 0x0a, 0x10, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68,
+	0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3e, 0x0a, 0x0a, 0x66, 0x69,
+	0x72, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x09, 0x66, 0x69,
+	0x72, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f,
+	0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e,
+	0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
+	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
+	0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x47, 0x0a, 0x06, 0x69, 0x6d, 0x61,
+	0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61,
+	0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c,
+	0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67,
+	0x65, 0x73, 0x12, 0x47, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
+	0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67,
+	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68,
+	0x61, 0x72, 0x74, 0x52, 0x06, 0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x12, 0x6c, 0x0a, 0x11, 0x76,
+	0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
+	0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
+	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e,
+	0x66, 0x6f, 0x48, 0x01, 0x52, 0x10, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x66, 0x69,
+	0x72, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x76, 0x65, 0x72,
+	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0xe8,
+	0x03, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x68,
+	0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68,
+	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
 	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
 	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12,
-	0x70, 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x63,
-	0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e,
+	0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65,
+	0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x72,
+	0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x12, 0x54, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74,
+	0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61,
+	0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c,
+	0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x65, 0x66, 0x65,
+	0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14,
+	0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70,
+	0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e,
+	0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x48, 0x01, 0x52, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68,
+	0x88, 0x01, 0x01, 0x12, 0x69, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70,
+	0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37,
+	0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69,
+	0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69,
+	0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74,
+	0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x02, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65,
+	0x6e, 0x74, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x14,
+	0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70,
+	0x68, 0x61, 0x73, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
+	0x5f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61,
+	0x6c, 0x74, 0x68, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f,
+	0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a, 0x11, 0x53, 0x74, 0x61,
+	0x67, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x12, 0x64, 0x0a, 0x0f, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d,
+	0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e,
 	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74,
 	0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e,
-	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69,
-	0x6f, 0x6e, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d, 0x73, 0x52, 0x13, 0x70, 0x72,
-	0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x63, 0x68, 0x61, 0x6e, 0x69, 0x73, 0x6d,
-	0x73, 0x12, 0x5f, 0x0a, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48,
-	0x00, 0x52, 0x0c, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88,
-	0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x22, 0xd0, 0x03, 0x0a, 0x07, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
-	0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
-	0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-	0x6b, 0x69, 0x6e, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
-	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
-	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e,
-	0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x02, 0x69, 0x64, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18,
-	0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
-	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
-	0x69, 0x74, 0x73, 0x12, 0x47, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
-	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
-	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49,
-	0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x06,
-	0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79,
-	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x06, 0x63,
-	0x68, 0x61, 0x72, 0x74, 0x73, 0x12, 0x4f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
-	0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
-	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06,
-	0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xd7, 0x03, 0x0a, 0x0d, 0x46, 0x72, 0x65, 0x69, 0x67,
-	0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x68, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x69,
-	0x66, 0x69, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e,
+	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x75,
+	0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x75, 0x70, 0x73, 0x74,
+	0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x77, 0x61,
+	0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x77,
+	0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x22, 0xb0, 0x02, 0x0a, 0x09, 0x57, 0x61, 0x72,
+	0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x5f, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x69,
+	0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x4e, 0x0a, 0x08, 0x6d,
+	0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e,
 	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74,
 	0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e,
-	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74,
-	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x49,
-	0x6e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64,
-	0x49, 0x6e, 0x12, 0x6b, 0x0a, 0x0c, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x66,
-	0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x48, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74,
+	0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x4b, 0x0a, 0x04, 0x73,
+	0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61,
+	0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c,
+	0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53, 0x70,
+	0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x51, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
 	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
 	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
-	0x68, 0x61, 0x31, 0x2e, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74,
-	0x72, 0x79, 0x52, 0x0b, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x1a,
-	0x76, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x49, 0x6e, 0x45, 0x6e, 0x74,
-	0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x03, 0x6b, 0x65, 0x79, 0x12, 0x4d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x68, 0x61, 0x31, 0x2e, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53, 0x74, 0x61,
+	0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x71, 0x0a, 0x0d, 0x57,
+	0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x60, 0x0a, 0x0d,
+	0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
 	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
-	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56,
-	0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x77, 0x0a, 0x10, 0x41, 0x70, 0x70, 0x72, 0x6f,
-	0x76, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
-	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4d, 0x0a,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79,
-	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64,
-	0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
-	0x22, 0x0f, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x53, 0x74, 0x61, 0x67,
-	0x65, 0x22, 0x0f, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x64, 0x53, 0x74, 0x61,
-	0x67, 0x65, 0x22, 0xd3, 0x03, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x46, 0x72, 0x65,
-	0x69, 0x67, 0x68, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x02, 0x69, 0x64, 0x12, 0x3e, 0x0a, 0x0a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x65,
-	0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
-	0x74, 0x61, 0x6d, 0x70, 0x48, 0x00, 0x52, 0x09, 0x66, 0x69, 0x72, 0x73, 0x74, 0x53, 0x65, 0x65,
-	0x6e, 0x88, 0x01, 0x01, 0x12, 0x4d, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18,
-	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x52,
+	0x65, 0x70, 0x6f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52,
+	0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x58,
+	0x0a, 0x0f, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x6f, 0x62, 0x73, 0x65, 0x72,
+	0x76, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x47, 0x65,
+	0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe7, 0x02, 0x0a, 0x0c, 0x56, 0x65, 0x72,
+	0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x72, 0x0a, 0x12, 0x61, 0x6e, 0x61,
+	0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
 	0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
 	0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
-	0x69, 0x74, 0x73, 0x12, 0x47, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
-	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
-	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49,
-	0x6d, 0x61, 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x06,
-	0x63, 0x68, 0x61, 0x72, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x67,
-	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79,
-	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x06, 0x63,
-	0x68, 0x61, 0x72, 0x74, 0x73, 0x12, 0x6c, 0x0a, 0x11, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
-	0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69,
-	0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x01, 0x52, 0x10,
-	0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f,
-	0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x65,
-	0x65, 0x6e, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0xe2, 0x03, 0x0a, 0x0b, 0x53, 0x74, 0x61,
-	0x67, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x65, 0x0a, 0x0f, 0x63, 0x75, 0x72, 0x72,
-	0x65, 0x6e, 0x74, 0x5f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
-	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x6d,
-	0x70, 0x6c, 0x65, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x63, 0x75,
-	0x72, 0x72, 0x65, 0x6e, 0x74, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x88, 0x01, 0x01, 0x12,
-	0x51, 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
-	0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x69, 0x6d, 0x70,
-	0x6c, 0x65, 0x46, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f,
-	0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c,
-	0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
-	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72,
-	0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
-	0x68, 0x61, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x48, 0x01, 0x52, 0x06, 0x68, 0x65,
-	0x61, 0x6c, 0x74, 0x68, 0x88, 0x01, 0x01, 0x12, 0x69, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65,
-	0x6e, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x37, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e,
-	0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67,
-	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x72,
-	0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x02, 0x52, 0x10, 0x63,
-	0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x88,
-	0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x63, 0x75, 0x72,
-	0x72, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x72, 0x65, 0x69, 0x67, 0x68, 0x74, 0x42, 0x09, 0x0a, 0x07,
-	0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x42, 0x14, 0x0a, 0x12, 0x5f, 0x63, 0x75, 0x72, 0x72,
-	0x65, 0x6e, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a,
-	0x11, 0x53, 0x74, 0x61, 0x67, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
-	0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x0d, 0x53, 0x75, 0x62, 0x73, 0x63,
-	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x64, 0x0a, 0x0f, 0x75, 0x70, 0x73, 0x74,
-	0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x3b, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
-	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x74, 0x61,
-	0x67, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e,
-	0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x67, 0x65, 0x73, 0x12, 0x1c,
-	0x0a, 0x09, 0x77, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x09, 0x77, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x22, 0xb0, 0x02, 0x0a,
-	0x09, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70,
-	0x69, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x0a, 0x61, 0x70, 0x69, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6b,
-	0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12,
-	0x4e, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x32, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61,
-	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x76, 0x31, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63,
-	0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-	0x4b, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e,
-	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74,
-	0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e,
-	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75,
-	0x73, 0x65, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x51, 0x0a, 0x06,
-	0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x67,
+	0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
+	0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x11, 0x61, 0x6e, 0x61, 0x6c,
+	0x79, 0x73, 0x69, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x76, 0x0a,
+	0x15, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x6d, 0x65,
+	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x67,
 	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79,
 	0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73,
-	0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
-	0x71, 0x0a, 0x0d, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53, 0x70, 0x65, 0x63,
-	0x12, 0x60, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-	0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
-	0x6e, 0x73, 0x22, 0x58, 0x0a, 0x0f, 0x57, 0x61, 0x72, 0x65, 0x68, 0x6f, 0x75, 0x73, 0x65, 0x53,
-	0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x6f,
-	0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76,
-	0x65, 0x64, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xe7, 0x02, 0x0a,
-	0x0c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x72, 0x0a,
-	0x12, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61,
-	0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x67, 0x69, 0x74, 0x68,
+	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
+	0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x13, 0x61,
+	0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x03, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
+	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41,
+	0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65,
+	0x6e, 0x74, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x61, 0x6e, 0x61,
+	0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x22, 0x2f, 0x0a, 0x19, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x65,
+	0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x22, 0xe5, 0x02, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
+	0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x61, 0x0a, 0x06, 0x6c,
+	0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x49, 0x2e, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e,
+	0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
+	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52,
+	0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
+	0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x70,
+	0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b,
+	0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41,
+	0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e,
+	0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
+	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
+	0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x3e, 0x0a, 0x10, 0x41,
+	0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
+	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
+	0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3f, 0x0a, 0x13, 0x41,
+	0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65,
+	0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x75, 0x0a, 0x10,
+	0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f,
+	0x12, 0x61, 0x0a, 0x0c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f,
+	0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
+	0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x66,
+	0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
+	0x52, 0x75, 0x6e, 0x22, 0x5e, 0x0a, 0x14, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52,
+	0x75, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e,
+	0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
+	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a,
+	0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x68,
+	0x61, 0x73, 0x65, 0x42, 0xad, 0x02, 0x0a, 0x2c, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68,
 	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61,
 	0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c,
-	0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x65, 0x6d,
-	0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x11,
-	0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
-	0x73, 0x12, 0x76, 0x0a, 0x15, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75,
-	0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x3d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b,
-	0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61,
-	0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c,
-	0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48,
-	0x00, 0x52, 0x13, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65,
-	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x88, 0x01, 0x01, 0x12, 0x51, 0x0a, 0x04, 0x61, 0x72, 0x67,
-	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x41, 0x72,
-	0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x42, 0x18, 0x0a, 0x16,
-	0x5f, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x6d, 0x65,
-	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x2f, 0x0a, 0x19, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73,
-	0x69, 0x73, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65,
-	0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xe5, 0x02, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c,
-	0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
-	0x61, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x49, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75,
-	0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70,
-	0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79,
-	0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x4c,
-	0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65,
-	0x6c, 0x73, 0x12, 0x70, 0x0a, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67,
-	0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
-	0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x4d, 0x65,
-	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0b, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e,
-	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a,
-	0x3e, 0x0a, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e,
-	0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22,
-	0x3f, 0x0a, 0x13, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x41, 0x72,
-	0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x22, 0x75, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x61, 0x0a, 0x0c, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73,
-	0x5f, 0x72, 0x75, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x67, 0x69, 0x74,
-	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x6b,
-	0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x61,
-	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x52, 0x75,
-	0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0b, 0x61, 0x6e, 0x61, 0x6c,
-	0x79, 0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x22, 0x5e, 0x0a, 0x14, 0x41, 0x6e, 0x61, 0x6c, 0x79,
-	0x73, 0x69, 0x73, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12,
-	0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a,
-	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x42, 0xad, 0x02, 0x0a, 0x2c, 0x63, 0x6f, 0x6d, 0x2e,
-	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x6b, 0x75, 0x69, 0x74,
-	0x79, 0x2e, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2e, 0x70, 0x6b, 0x67, 0x2e, 0x61, 0x70, 0x69, 0x2e,
-	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50,
-	0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x61, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2f, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2f,
-	0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
-	0xa2, 0x02, 0x06, 0x47, 0x43, 0x41, 0x4b, 0x50, 0x41, 0xaa, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68,
-	0x75, 0x62, 0x2e, 0x43, 0x6f, 0x6d, 0x2e, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x4b, 0x61,
-	0x72, 0x67, 0x6f, 0x2e, 0x50, 0x6b, 0x67, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x61, 0x6c,
-	0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f,
-	0x6d, 0x5c, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50,
-	0x6b, 0x67, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2,
-	0x02, 0x34, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b, 0x75,
-	0x69, 0x74, 0x79, 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41, 0x70,
-	0x69, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65,
-	0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x3a,
-	0x3a, 0x43, 0x6f, 0x6d, 0x3a, 0x3a, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x3a, 0x3a, 0x4b, 0x61,
-	0x72, 0x67, 0x6f, 0x3a, 0x3a, 0x50, 0x6b, 0x67, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56,
-	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x70, 0x68, 0x61, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f,
+	0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61,
+	0x6b, 0x75, 0x69, 0x74, 0x79, 0x2f, 0x6b, 0x61, 0x72, 0x67, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f,
+	0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x06, 0x47,
+	0x43, 0x41, 0x4b, 0x50, 0x41, 0xaa, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x43,
+	0x6f, 0x6d, 0x2e, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x2e, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x2e,
+	0x50, 0x6b, 0x67, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
+	0xca, 0x02, 0x28, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b,
+	0x75, 0x69, 0x74, 0x79, 0x5c, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41,
+	0x70, 0x69, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x34, 0x47, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x5c, 0x43, 0x6f, 0x6d, 0x5c, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x5c,
+	0x4b, 0x61, 0x72, 0x67, 0x6f, 0x5c, 0x50, 0x6b, 0x67, 0x5c, 0x41, 0x70, 0x69, 0x5c, 0x56, 0x31,
+	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
+	0x74, 0x61, 0xea, 0x02, 0x2e, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x3a, 0x3a, 0x43, 0x6f, 0x6d,
+	0x3a, 0x3a, 0x41, 0x6b, 0x75, 0x69, 0x74, 0x79, 0x3a, 0x3a, 0x4b, 0x61, 0x72, 0x67, 0x6f, 0x3a,
+	0x3a, 0x50, 0x6b, 0x67, 0x3a, 0x3a, 0x41, 0x70, 0x69, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70,
+	0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -4068,7 +4069,7 @@ var file_v1alpha1_types_proto_goTypes = []interface{}{
 	(*FreightStatus)(nil),                 // 38: github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus
 	(*VerifiedStage)(nil),                 // 39: github.com.akuity.kargo.pkg.api.v1alpha1.VerifiedStage
 	(*ApprovedStage)(nil),                 // 40: github.com.akuity.kargo.pkg.api.v1alpha1.ApprovedStage
-	(*SimpleFreight)(nil),                 // 41: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight
+	(*FreightReference)(nil),              // 41: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference
 	(*StageStatus)(nil),                   // 42: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus
 	(*StageSubscription)(nil),             // 43: github.com.akuity.kargo.pkg.api.v1alpha1.StageSubscription
 	(*Subscriptions)(nil),                 // 44: github.com.akuity.kargo.pkg.api.v1alpha1.Subscriptions
@@ -4110,7 +4111,7 @@ var file_v1alpha1_types_proto_depIdxs = []int32{
 	59, // 16: github.com.akuity.kargo.pkg.api.v1alpha1.Promotion.metadata:type_name -> github.com.akuity.kargo.pkg.api.metav1.ObjectMeta
 	31, // 17: github.com.akuity.kargo.pkg.api.v1alpha1.Promotion.spec:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.PromotionSpec
 	32, // 18: github.com.akuity.kargo.pkg.api.v1alpha1.Promotion.status:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.PromotionStatus
-	41, // 19: github.com.akuity.kargo.pkg.api.v1alpha1.PromotionInfo.freight:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight
+	41, // 19: github.com.akuity.kargo.pkg.api.v1alpha1.PromotionInfo.freight:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference
 	60, // 20: github.com.akuity.kargo.pkg.api.v1alpha1.PromotionList.metadata:type_name -> github.com.akuity.kargo.pkg.api.metav1.ListMeta
 	25, // 21: github.com.akuity.kargo.pkg.api.v1alpha1.PromotionList.items:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Promotion
 	10, // 22: github.com.akuity.kargo.pkg.api.v1alpha1.PromotionMechanisms.git_repo_updates:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.GitRepoUpdate
@@ -4137,13 +4138,13 @@ var file_v1alpha1_types_proto_depIdxs = []int32{
 	38, // 43: github.com.akuity.kargo.pkg.api.v1alpha1.Freight.status:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus
 	55, // 44: github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus.verified_in:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus.VerifiedInEntry
 	56, // 45: github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus.approved_for:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightStatus.ApprovedForEntry
-	61, // 46: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight.first_seen:type_name -> google.protobuf.Timestamp
-	9,  // 47: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight.commits:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.GitCommit
-	21, // 48: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight.images:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Image
-	7,  // 49: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight.charts:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Chart
-	52, // 50: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight.verification_info:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.VerificationInfo
-	41, // 51: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.current_freight:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight
-	41, // 52: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.history:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight
+	61, // 46: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference.first_seen:type_name -> google.protobuf.Timestamp
+	9,  // 47: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference.commits:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.GitCommit
+	21, // 48: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference.images:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Image
+	7,  // 49: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference.charts:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Chart
+	52, // 50: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference.verification_info:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.VerificationInfo
+	41, // 51: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.current_freight:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference
+	41, // 52: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.history:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference
 	12, // 53: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.health:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.Health
 	26, // 54: github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus.current_promotion:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.PromotionInfo
 	43, // 55: github.com.akuity.kargo.pkg.api.v1alpha1.Subscriptions.upstream_stages:type_name -> github.com.akuity.kargo.pkg.api.v1alpha1.StageSubscription
@@ -4665,7 +4666,7 @@ func file_v1alpha1_types_proto_init() {
 			}
 		}
 		file_v1alpha1_types_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SimpleFreight); i {
+			switch v := v.(*FreightReference); i {
 			case 0:
 				return &v.state
 			case 1:
diff --git a/ui/src/app.tsx b/ui/src/app.tsx
index b7d20055..29ffd73d 100644
--- a/ui/src/app.tsx
+++ b/ui/src/app.tsx
@@ -4,7 +4,6 @@ import { ConfigProvider } from 'antd';
 import { BrowserRouter, Route, Routes } from 'react-router-dom';
 
 import { transport } from '@ui/config/transport';
-import { ModalContextProvider } from '@ui/features/common/modal/modal-context';
 import { Project } from '@ui/pages/project';
 
 import { paths } from './config/paths';
@@ -24,23 +23,21 @@ export const App = () => (
   <TransportProvider transport={transport}>
     <QueryClientProvider client={queryClient}>
       <ConfigProvider theme={themeConfig}>
-        <ModalContextProvider>
-          <AuthContextProvider>
-            <BrowserRouter>
-              <Routes>
-                <Route element={<ProtectedRoute />}>
-                  <Route element={<MainLayout />}>
-                    <Route path={paths.projects} element={<Projects />} />
-                    <Route path={paths.project} element={<Project />} />
-                    <Route path={paths.stage} element={<Project />} />
-                  </Route>
+        <AuthContextProvider>
+          <BrowserRouter>
+            <Routes>
+              <Route element={<ProtectedRoute />}>
+                <Route element={<MainLayout />}>
+                  <Route path={paths.projects} element={<Projects />} />
+                  <Route path={paths.project} element={<Project />} />
+                  <Route path={paths.stage} element={<Project />} />
                 </Route>
-                <Route path={paths.login} element={<Login />} />
-                <Route path={paths.tokenRenew} element={<TokenRenew />} />
-              </Routes>
-            </BrowserRouter>
-          </AuthContextProvider>
-        </ModalContextProvider>
+              </Route>
+              <Route path={paths.login} element={<Login />} />
+              <Route path={paths.tokenRenew} element={<TokenRenew />} />
+            </Routes>
+          </BrowserRouter>
+        </AuthContextProvider>
       </ConfigProvider>
     </QueryClientProvider>
   </TransportProvider>
diff --git a/ui/src/features/auth/protected-route.tsx b/ui/src/features/auth/protected-route.tsx
index 38944ab0..3c9a2032 100644
--- a/ui/src/features/auth/protected-route.tsx
+++ b/ui/src/features/auth/protected-route.tsx
@@ -1,9 +1,12 @@
 import { TransportProvider } from '@bufbuild/connect-query';
+import { useEffect, useRef, useState } from 'react';
 import { Navigate, Outlet } from 'react-router-dom';
 
 import { paths } from '@ui/config/paths';
 import { transportWithAuth } from '@ui/config/transport';
 
+import { ModalContextProvider } from '../common/modal/modal-context';
+
 import { useAuthContext } from './context/use-auth-context';
 
 export const ProtectedRoute = () => {
@@ -13,9 +16,21 @@ export const ProtectedRoute = () => {
     return <Navigate to={paths.login} replace />;
   }
 
+  const modalRef = useRef<HTMLDivElement>(null);
+  const [modalRoot, setModalRoot] = useState<HTMLDivElement | null>(null);
+  useEffect(() => {
+    setModalRoot(modalRef.current);
+  }, []);
+
   return (
     <TransportProvider transport={transportWithAuth}>
-      <Outlet />
+      <div ref={modalRef}>
+        {modalRoot && (
+          <ModalContextProvider container={modalRoot}>
+            <Outlet />
+          </ModalContextProvider>
+        )}
+      </div>
     </TransportProvider>
   );
 };
diff --git a/ui/src/features/common/freight-label.tsx b/ui/src/features/common/freight-label.tsx
index ae43982a..2d91faa5 100644
--- a/ui/src/features/common/freight-label.tsx
+++ b/ui/src/features/common/freight-label.tsx
@@ -1,3 +1,5 @@
+import { faCheck, faClipboard } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from 'antd';
 import { formatDistance } from 'date-fns';
 import { useEffect, useState } from 'react';
@@ -6,13 +8,21 @@ import { Freight } from '@ui/gen/v1alpha1/types_pb';
 
 const ALIAS_LABEL_KEY = 'kargo.akuity.io/alias';
 
-export const getAlias = (freight: Freight): string | undefined => {
-  return freight?.metadata?.labels[ALIAS_LABEL_KEY];
+export const getAlias = (freight?: Freight): string | undefined => {
+  return freight?.metadata?.labels[ALIAS_LABEL_KEY] || undefined;
 };
 
 export const FreightLabel = ({ freight }: { freight?: Freight }) => {
   const [id, setId] = useState<string>('');
   const [alias, setAlias] = useState<string | undefined>();
+  const [copied, setCopied] = useState<boolean>(false);
+
+  useEffect(() => {
+    if (copied) {
+      const timeout = setTimeout(() => setCopied(false), 1000);
+      return () => clearTimeout(timeout);
+    }
+  }, [copied]);
 
   useEffect(() => {
     setAlias(freight?.metadata?.labels[ALIAS_LABEL_KEY]);
@@ -20,27 +30,45 @@ export const FreightLabel = ({ freight }: { freight?: Freight }) => {
   }, [freight]);
 
   return (
-    <span className='truncate'>
+    <div
+      className='truncate cursor-pointer'
+      onClick={() => {
+        navigator.clipboard.writeText(alias || id);
+        setCopied(true);
+      }}
+    >
       <Tooltip
         title={
           <>
-            {alias && <div>Alias: {alias}</div>}
-            <div>ID: {id}</div>
-            <div>
-              {freight?.metadata?.creationTimestamp && (
-                <>
-                  Created:{' '}
+            <div className='uppercase text-xs w-full text-center font-semibold text-gray-400'>
+              <FontAwesomeIcon icon={copied ? faCheck : faClipboard} className='mr-2' />
+              {copied ? 'Copied' : `Click to copy ${alias ? 'alias' : 'id'}`}
+            </div>
+            {alias && <Info title='Alias'>{alias}</Info>}
+            <Info title='ID'>
+              <div className='font-mono'>{id}</div>
+            </Info>
+            {freight?.metadata?.creationTimestamp && (
+              <Info title='Created'>
+                <div className='text-right'>
                   {formatDistance(freight?.metadata?.creationTimestamp?.toDate(), new Date(), {
                     addSuffix: true
                   })}
-                </>
-              )}
-            </div>
+                </div>
+              </Info>
+            )}
           </>
         }
       >
-        {alias || id}
+        <div className='hover:text-gray-100'>{alias || id}</div>
       </Tooltip>
-    </span>
+    </div>
   );
 };
+
+const Info = ({ title, children }: { title: string; children: React.ReactNode }) => (
+  <div className='flex items-center my-1'>
+    <div className='text-xs text-gray-400 mr-4'>{title}</div>
+    <div className='text-sm ml-auto'>{children}</div>
+  </div>
+);
diff --git a/ui/src/features/common/modal/modal-context.tsx b/ui/src/features/common/modal/modal-context.tsx
index 6bbf50a2..48f15374 100644
--- a/ui/src/features/common/modal/modal-context.tsx
+++ b/ui/src/features/common/modal/modal-context.tsx
@@ -24,9 +24,10 @@ interface ModalContextValue {
 export const ModalContext = React.createContext<ModalContextValue | null>(null);
 interface ModalProviderProps {
   children: React.ReactNode;
+  container: HTMLElement | null;
 }
 
-export const ModalContextProvider = ({ children }: ModalProviderProps) => {
+export const ModalContextProvider = ({ children, container }: ModalProviderProps) => {
   const [modals, setModals] = React.useState<Record<string, ModalItem>>({});
 
   const show = (key: string, modal: ModalComponent) => {
@@ -74,7 +75,7 @@ export const ModalContextProvider = ({ children }: ModalProviderProps) => {
       {/* REACT SPECIFIC BUG - https://github.com/remix-run/react-router/issues/8834#issuecomment-1118083034 */}
       <div className='h-full'>
         {children}
-        <Portal>
+        <Portal container={container}>
           <>
             {Object.keys(modals).map((key) => {
               const { component: Component, visible } = modals[key];
diff --git a/ui/src/features/common/portal.tsx b/ui/src/features/common/portal.tsx
index 769e8ffb..4b756597 100644
--- a/ui/src/features/common/portal.tsx
+++ b/ui/src/features/common/portal.tsx
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom';
 
 type PortalProps = {
   children: React.ReactElement;
-  container?: HTMLElement;
+  container?: HTMLElement | null;
   disablePortal?: boolean;
 };
 
diff --git a/ui/src/features/project/project-details/nodes/stage-node.tsx b/ui/src/features/project/project-details/nodes/stage-node.tsx
index 6b0a876a..580dc97f 100644
--- a/ui/src/features/project/project-details/nodes/stage-node.tsx
+++ b/ui/src/features/project/project-details/nodes/stage-node.tsx
@@ -101,9 +101,9 @@ export const StageNode = ({
           ) : (
             <>
               <h3>Current Freight</h3>
-              <p className='font-mono text-sm font-semibold h-full flex items-center justify-center'>
+              <div className='font-mono text-sm font-semibold h-full flex items-center justify-center'>
                 <FreightLabel freight={currentFreight} />
-              </p>
+              </div>
             </>
           )}
         </div>
diff --git a/ui/src/features/project/project-details/project-details.tsx b/ui/src/features/project/project-details/project-details.tsx
index 761f5d2b..86d87776 100644
--- a/ui/src/features/project/project-details/project-details.tsx
+++ b/ui/src/features/project/project-details/project-details.tsx
@@ -9,6 +9,7 @@ import {
   faEllipsisV,
   faEye,
   faEyeSlash,
+  faPencil,
   faRefresh,
   faTimeline
 } from '@fortawesome/free-solid-svg-icons';
@@ -23,6 +24,7 @@ import { transportWithAuth } from '@ui/config/transport';
 import { ColorContext } from '@ui/context/colors';
 import { LoadingState } from '@ui/features/common';
 import { getAlias } from '@ui/features/common/freight-label';
+import { useModal } from '@ui/features/common/modal/use-modal';
 import { ConfirmPromotionDialogue } from '@ui/features/freightline/confirm-promotion-dialogue';
 import { FreightContents } from '@ui/features/freightline/freight-contents';
 import { FreightItem, FreightMode } from '@ui/features/freightline/freight-item';
@@ -55,6 +57,7 @@ import { RepoNode } from './nodes/repo-node';
 import { Nodule, StageNode } from './nodes/stage-node';
 import styles from './project-details.module.less';
 import { NodeType, NodesItemType } from './types';
+import { UpdateFreightAliasModal } from './update-freight-alias-modal';
 
 const lineThickness = 2;
 const nodeWidth = 150;
@@ -68,9 +71,11 @@ const getSeconds = (ts?: Timestamp): number => Number(ts?.seconds) || 0;
 export const ProjectDetails = () => {
   const { name, stageName } = useParams();
   const { data, isLoading } = useQuery(listStages.useQuery({ project: name }));
-  const { data: freightData, isLoading: isLoadingFreight } = useQuery(
-    queryFreight.useQuery({ project: name })
-  );
+  const {
+    data: freightData,
+    isLoading: isLoadingFreight,
+    refetch: refetchFreightData
+  } = useQuery(queryFreight.useQuery({ project: name }));
 
   const { data: warehouseData, isLoading: isLoadingWarehouses } = useQuery(
     listWarehouses.useQuery({ project: name })
@@ -91,6 +96,7 @@ export const ProjectDetails = () => {
     onSuccess: () => {
       message.success('Warehouse successfully refreshed');
       setPromotingStage(undefined);
+      refetchFreightData();
     }
   });
 
@@ -100,6 +106,8 @@ export const ProjectDetails = () => {
     false
   );
 
+  const { show } = useModal();
+
   React.useEffect(() => {
     if (!data || !isVisible || !warehouseData) {
       return;
@@ -141,7 +149,7 @@ export const ProjectDetails = () => {
     watchStages();
 
     const watchWarehouses = async () => {
-      const promiseClient = createPromiseClient(KargoService, transport);
+      const promiseClient = createPromiseClient(KargoService, transportWithAuth);
       const stream = promiseClient.watchWarehouses({ project: name }, { signal: cancel.signal });
       let warehouses = warehouseData?.warehouses || [];
       const refresh = {} as { [key: string]: boolean };
@@ -667,7 +675,28 @@ export const ProjectDetails = () => {
                                   message.success('Copied Freight Alias to clipboard');
                                 }
                               }
-                            : null
+                            : null,
+                          {
+                            key: '4',
+                            label: (
+                              <>
+                                <FontAwesomeIcon icon={faPencil} className='mr-2' /> Change Alias
+                              </>
+                            ),
+                            onClick: async () => {
+                              show((p) => (
+                                <UpdateFreightAliasModal
+                                  {...p}
+                                  freight={f || undefined}
+                                  project={name || ''}
+                                  onSubmit={() => {
+                                    refetchFreightData();
+                                    p.hide();
+                                  }}
+                                />
+                              ));
+                            }
+                          }
                         ]
                       }}
                     >
diff --git a/ui/src/features/project/project-details/update-freight-alias-modal.tsx b/ui/src/features/project/project-details/update-freight-alias-modal.tsx
new file mode 100644
index 00000000..be410981
--- /dev/null
+++ b/ui/src/features/project/project-details/update-freight-alias-modal.tsx
@@ -0,0 +1,75 @@
+import { faPencil } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useMutation } from '@tanstack/react-query';
+import { Input, Modal, message } from 'antd';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+import { FieldContainer } from '@ui/features/common/form/field-container';
+import { getAlias } from '@ui/features/common/freight-label';
+import { ModalComponentProps } from '@ui/features/common/modal/modal-context';
+import { updateFreightAlias } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';
+import { Freight } from '@ui/gen/v1alpha1/types_pb';
+import { zodValidators } from '@ui/utils/validators';
+
+type Props = ModalComponentProps & {
+  freight: Freight;
+  project: string;
+  onSubmit: () => void;
+};
+
+const formSchema = z.object({
+  value: zodValidators.requiredString
+});
+
+export const UpdateFreightAliasModal = ({ freight, project, onSubmit, hide, ...props }: Props) => {
+  const { control, handleSubmit } = useForm({
+    defaultValues: {
+      value: ''
+    },
+    resolver: zodResolver(formSchema)
+  });
+
+  const { mutateAsync: updateAliasAction } = useMutation({
+    ...updateFreightAlias.useMutation(),
+    onError: (err) => {
+      message.error(err?.toString());
+    },
+    onSuccess: () => {
+      message.success('Alias successfully updated');
+    }
+  });
+
+  return (
+    <Modal
+      {...props}
+      title={
+        <>
+          <FontAwesomeIcon icon={faPencil} className='mr-2' />
+          Update Alias
+        </>
+      }
+      onCancel={hide}
+      okText='Submit'
+      onOk={handleSubmit(async (data) => {
+        await updateAliasAction({
+          project,
+          freight: freight?.id || '',
+          alias: data.value || ''
+        });
+        onSubmit();
+      })}
+    >
+      <div className='mb-4'>
+        <div className='text-xs font-semibold uppercase'>Freight ID</div>
+        <div className='font-mono'>{freight?.id}</div>
+      </div>
+      <FieldContainer label='New Alias' name='value' control={control}>
+        {({ field }) => (
+          <Input {...field} type='text' placeholder={getAlias(freight) || 'New alias'} />
+        )}
+      </FieldContainer>
+    </Modal>
+  );
+};
diff --git a/ui/src/features/stage/stage-actions.tsx b/ui/src/features/stage/stage-actions.tsx
index 5b7a1a20..b1652914 100644
--- a/ui/src/features/stage/stage-actions.tsx
+++ b/ui/src/features/stage/stage-actions.tsx
@@ -50,7 +50,7 @@ export const StageActions = ({ stage }: { stage: Stage }) => {
     ) : null
   );
 
-  // Once the Refresh process is done, refetch Freigths list
+  // Once the Refresh process is done, refetch Freight list
   React.useEffect(() => {
     if (stage?.metadata?.annotations['kargo.akuity.io/refresh']) {
       setShouldRefetchFreights(true);
diff --git a/ui/src/gen/schema/projects.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/projects.kargo.akuity.io_v1alpha1.json
new file mode 100644
index 00000000..6b591d4c
--- /dev/null
+++ b/ui/src/gen/schema/projects.kargo.akuity.io_v1alpha1.json
@@ -0,0 +1,29 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "description": "Project is a resource type that reconciles to a specially labeled namespace and other TODO: TBD project-level resources.",
+  "properties": {
+    "apiVersion": {
+      "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
+      "type": "string"
+    },
+    "kind": {
+      "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
+      "type": "string"
+    },
+    "metadata": {
+      "type": "object"
+    },
+    "spec": {
+      "description": "Spec describes a Project.",
+      "type": "object"
+    },
+    "status": {
+      "description": "Status describes the Project's current status.",
+      "type": "object"
+    }
+  },
+  "required": [
+    "spec"
+  ],
+  "type": "object"
+}
\ No newline at end of file
diff --git a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
index 0e2371c4..d5bbe9aa 100644
--- a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
@@ -232,6 +232,10 @@
                     },
                     "type": "object"
                   },
+                  "insecureSkipTLSVerify": {
+                    "description": "InsecureSkipTLSVerify specifies whether certificate verification errors should be ignored when connecting to the repository. This should be enabled only with great caution.",
+                    "type": "boolean"
+                  },
                   "kustomize": {
                     "description": "Kustomize describes how to use Kustomize to incorporate Freight into the Stage. This is mutually exclusive with the Render and Helm fields.",
                     "properties": {
@@ -784,7 +788,7 @@
         "history": {
           "description": "History is a stack of recent Freight. By default, the last ten Freight are stored.",
           "items": {
-            "description": "SimpleFreight is a simplified representation of a piece of Freight -- not a root resource type.",
+            "description": "FreightReference is a simplified representation of a piece of Freight -- not a root resource type.",
             "properties": {
               "charts": {
                 "description": "Charts describes specific versions of specific Helm charts.",
diff --git a/ui/src/gen/schema/warehouses.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/warehouses.kargo.akuity.io_v1alpha1.json
index ce4537c9..d72a114c 100644
--- a/ui/src/gen/schema/warehouses.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/warehouses.kargo.akuity.io_v1alpha1.json
@@ -55,6 +55,10 @@
                     "pattern": "^\\w+([-/]\\w+)*$",
                     "type": "string"
                   },
+                  "insecureSkipTLSVerify": {
+                    "description": "InsecureSkipTLSVerify specifies whether certificate verification errors should be ignored when connecting to the repository. This should be enabled only with great caution.",
+                    "type": "boolean"
+                  },
                   "repoURL": {
                     "description": "URL is the repository's URL. This is a required field.",
                     "minLength": 1,
diff --git a/ui/src/gen/v1alpha1/types_pb.ts b/ui/src/gen/v1alpha1/types_pb.ts
index ba8ef525..878771eb 100644
--- a/ui/src/gen/v1alpha1/types_pb.ts
+++ b/ui/src/gen/v1alpha1/types_pb.ts
@@ -1279,9 +1279,9 @@ export class PromotionInfo extends Message<PromotionInfo> {
   name = "";
 
   /**
-   * @generated from field: github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight freight = 2;
+   * @generated from field: github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference freight = 2;
    */
-  freight?: SimpleFreight;
+  freight?: FreightReference;
 
   constructor(data?: PartialMessage<PromotionInfo>) {
     super();
@@ -1292,7 +1292,7 @@ export class PromotionInfo extends Message<PromotionInfo> {
   static readonly typeName = "github.com.akuity.kargo.pkg.api.v1alpha1.PromotionInfo";
   static readonly fields: FieldList = proto3.util.newFieldList(() => [
     { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
-    { no: 2, name: "freight", kind: "message", T: SimpleFreight },
+    { no: 2, name: "freight", kind: "message", T: FreightReference },
   ]);
 
   static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): PromotionInfo {
@@ -1981,9 +1981,9 @@ export class ApprovedStage extends Message<ApprovedStage> {
 }
 
 /**
- * @generated from message github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight
+ * @generated from message github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference
  */
-export class SimpleFreight extends Message<SimpleFreight> {
+export class FreightReference extends Message<FreightReference> {
   /**
    * @generated from field: string id = 1;
    */
@@ -2014,13 +2014,13 @@ export class SimpleFreight extends Message<SimpleFreight> {
    */
   verificationInfo?: VerificationInfo;
 
-  constructor(data?: PartialMessage<SimpleFreight>) {
+  constructor(data?: PartialMessage<FreightReference>) {
     super();
     proto3.util.initPartial(data, this);
   }
 
   static readonly runtime: typeof proto3 = proto3;
-  static readonly typeName = "github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight";
+  static readonly typeName = "github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference";
   static readonly fields: FieldList = proto3.util.newFieldList(() => [
     { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
     { no: 2, name: "first_seen", kind: "message", T: Timestamp, opt: true },
@@ -2030,20 +2030,20 @@ export class SimpleFreight extends Message<SimpleFreight> {
     { no: 7, name: "verification_info", kind: "message", T: VerificationInfo, opt: true },
   ]);
 
-  static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): SimpleFreight {
-    return new SimpleFreight().fromBinary(bytes, options);
+  static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): FreightReference {
+    return new FreightReference().fromBinary(bytes, options);
   }
 
-  static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): SimpleFreight {
-    return new SimpleFreight().fromJson(jsonValue, options);
+  static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): FreightReference {
+    return new FreightReference().fromJson(jsonValue, options);
   }
 
-  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): SimpleFreight {
-    return new SimpleFreight().fromJsonString(jsonString, options);
+  static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): FreightReference {
+    return new FreightReference().fromJsonString(jsonString, options);
   }
 
-  static equals(a: SimpleFreight | PlainMessage<SimpleFreight> | undefined, b: SimpleFreight | PlainMessage<SimpleFreight> | undefined): boolean {
-    return proto3.util.equals(SimpleFreight, a, b);
+  static equals(a: FreightReference | PlainMessage<FreightReference> | undefined, b: FreightReference | PlainMessage<FreightReference> | undefined): boolean {
+    return proto3.util.equals(FreightReference, a, b);
   }
 }
 
@@ -2052,14 +2052,14 @@ export class SimpleFreight extends Message<SimpleFreight> {
  */
 export class StageStatus extends Message<StageStatus> {
   /**
-   * @generated from field: optional github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight current_freight = 2;
+   * @generated from field: optional github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference current_freight = 2;
    */
-  currentFreight?: SimpleFreight;
+  currentFreight?: FreightReference;
 
   /**
-   * @generated from field: repeated github.com.akuity.kargo.pkg.api.v1alpha1.SimpleFreight history = 3;
+   * @generated from field: repeated github.com.akuity.kargo.pkg.api.v1alpha1.FreightReference history = 3;
    */
-  history: SimpleFreight[] = [];
+  history: FreightReference[] = [];
 
   /**
    * @generated from field: string error = 4;
@@ -2089,8 +2089,8 @@ export class StageStatus extends Message<StageStatus> {
   static readonly runtime: typeof proto3 = proto3;
   static readonly typeName = "github.com.akuity.kargo.pkg.api.v1alpha1.StageStatus";
   static readonly fields: FieldList = proto3.util.newFieldList(() => [
-    { no: 2, name: "current_freight", kind: "message", T: SimpleFreight, opt: true },
-    { no: 3, name: "history", kind: "message", T: SimpleFreight, repeated: true },
+    { no: 2, name: "current_freight", kind: "message", T: FreightReference, opt: true },
+    { no: 3, name: "history", kind: "message", T: FreightReference, repeated: true },
     { no: 4, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */ },
     { no: 5, name: "health", kind: "message", T: Health, opt: true },
     { no: 6, name: "current_promotion", kind: "message", T: PromotionInfo, opt: true },


Test patch:
diff --git a/api/v1alpha1/stage_types_test.go b/api/v1alpha1/stage_types_test.go
index 28de4e5b..f489a3bd 100644
--- a/api/v1alpha1/stage_types_test.go
+++ b/api/v1alpha1/stage_types_test.go
@@ -6,10 +6,10 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestSimpleFreightStackEmpty(t *testing.T) {
+func TestFreightReferenceStackEmpty(t *testing.T) {
 	testCases := []struct {
 		name           string
-		stack          SimpleFreightStack
+		stack          FreightReferenceStack
 		expectedResult bool
 	}{
 		{
@@ -19,12 +19,12 @@ func TestSimpleFreightStackEmpty(t *testing.T) {
 		},
 		{
 			name:           "stack is empty",
-			stack:          SimpleFreightStack{},
+			stack:          FreightReferenceStack{},
 			expectedResult: true,
 		},
 		{
 			name:           "stack has items",
-			stack:          SimpleFreightStack{{ID: "foo"}},
+			stack:          FreightReferenceStack{{ID: "foo"}},
 			expectedResult: false,
 		},
 	}
@@ -35,33 +35,33 @@ func TestSimpleFreightStackEmpty(t *testing.T) {
 	}
 }
 
-func TestSimpleFreightStackPop(t *testing.T) {
+func TestFreightReferenceStackPop(t *testing.T) {
 	testCases := []struct {
 		name            string
-		stack           SimpleFreightStack
-		expectedStack   SimpleFreightStack
-		expectedFreight SimpleFreight
+		stack           FreightReferenceStack
+		expectedStack   FreightReferenceStack
+		expectedFreight FreightReference
 		expectedOK      bool
 	}{
 		{
 			name:            "stack is nil",
 			stack:           nil,
 			expectedStack:   nil,
-			expectedFreight: SimpleFreight{},
+			expectedFreight: FreightReference{},
 			expectedOK:      false,
 		},
 		{
 			name:            "stack is empty",
-			stack:           SimpleFreightStack{},
-			expectedStack:   SimpleFreightStack{},
-			expectedFreight: SimpleFreight{},
+			stack:           FreightReferenceStack{},
+			expectedStack:   FreightReferenceStack{},
+			expectedFreight: FreightReference{},
 			expectedOK:      false,
 		},
 		{
 			name:            "stack has items",
-			stack:           SimpleFreightStack{{ID: "foo"}, {ID: "bar"}},
-			expectedStack:   SimpleFreightStack{{ID: "bar"}},
-			expectedFreight: SimpleFreight{ID: "foo"},
+			stack:           FreightReferenceStack{{ID: "foo"}, {ID: "bar"}},
+			expectedStack:   FreightReferenceStack{{ID: "bar"}},
+			expectedFreight: FreightReference{ID: "foo"},
 			expectedOK:      true,
 		},
 	}
@@ -75,29 +75,29 @@ func TestSimpleFreightStackPop(t *testing.T) {
 	}
 }
 
-func TestSimpleFreightStackTop(t *testing.T) {
+func TestFreightReferenceStackTop(t *testing.T) {
 	testCases := []struct {
 		name            string
-		stack           SimpleFreightStack
-		expectedFreight SimpleFreight
+		stack           FreightReferenceStack
+		expectedFreight FreightReference
 		expectedOK      bool
 	}{
 		{
 			name:            "stack is nil",
 			stack:           nil,
-			expectedFreight: SimpleFreight{},
+			expectedFreight: FreightReference{},
 			expectedOK:      false,
 		},
 		{
 			name:            "stack is empty",
-			stack:           SimpleFreightStack{},
-			expectedFreight: SimpleFreight{},
+			stack:           FreightReferenceStack{},
+			expectedFreight: FreightReference{},
 			expectedOK:      false,
 		},
 		{
 			name:            "stack has items",
-			stack:           SimpleFreightStack{{ID: "foo"}, {ID: "bar"}},
-			expectedFreight: SimpleFreight{ID: "foo"},
+			stack:           FreightReferenceStack{{ID: "foo"}, {ID: "bar"}},
+			expectedFreight: FreightReference{ID: "foo"},
 			expectedOK:      true,
 		},
 	}
@@ -112,32 +112,32 @@ func TestSimpleFreightStackTop(t *testing.T) {
 	}
 }
 
-func TestSimpleFreightStackPush(t *testing.T) {
+func TestFreightReferenceStackPush(t *testing.T) {
 	testCases := []struct {
 		name          string
-		stack         SimpleFreightStack
-		newFreight    []SimpleFreight
-		expectedStack SimpleFreightStack
+		stack         FreightReferenceStack
+		newFreight    []FreightReference
+		expectedStack FreightReferenceStack
 	}{
 		{
 			name:          "initial stack is nil",
 			stack:         nil,
-			newFreight:    []SimpleFreight{{ID: "foo"}, {ID: "bar"}},
-			expectedStack: SimpleFreightStack{{ID: "foo"}, {ID: "bar"}},
+			newFreight:    []FreightReference{{ID: "foo"}, {ID: "bar"}},
+			expectedStack: FreightReferenceStack{{ID: "foo"}, {ID: "bar"}},
 		},
 		{
 			name:          "initial stack is not nil",
-			stack:         SimpleFreightStack{{ID: "foo"}},
-			newFreight:    []SimpleFreight{{ID: "bar"}},
-			expectedStack: SimpleFreightStack{{ID: "bar"}, {ID: "foo"}},
+			stack:         FreightReferenceStack{{ID: "foo"}},
+			newFreight:    []FreightReference{{ID: "bar"}},
+			expectedStack: FreightReferenceStack{{ID: "bar"}, {ID: "foo"}},
 		},
 		{
 			name: "initial stack is full",
-			stack: SimpleFreightStack{
+			stack: FreightReferenceStack{
 				{}, {}, {}, {}, {}, {}, {}, {}, {}, {},
 			},
-			newFreight: []SimpleFreight{{ID: "foo"}},
-			expectedStack: SimpleFreightStack{
+			newFreight: []FreightReference{{ID: "foo"}},
+			expectedStack: FreightReferenceStack{
 				{ID: "foo"}, {}, {}, {}, {}, {}, {}, {}, {}, {},
 			},
 		},
diff --git a/internal/controller/promotion/argocd_test.go b/internal/controller/promotion/argocd_test.go
index a69721e3..27f97bcc 100644
--- a/internal/controller/promotion/argocd_test.go
+++ b/internal/controller/promotion/argocd_test.go
@@ -35,11 +35,11 @@ func TestArgoCDPromote(t *testing.T) {
 		name       string
 		promoMech  *argoCDMechanism
 		stage      *kargoapi.Stage
-		newFreight kargoapi.SimpleFreight
+		newFreight kargoapi.FreightReference
 		assertions func(
 			newStatus *kargoapi.PromotionStatus,
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -53,8 +53,8 @@ func TestArgoCDPromote(t *testing.T) {
 			},
 			assertions: func(
 				newStatus *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -68,7 +68,7 @@ func TestArgoCDPromote(t *testing.T) {
 					context.Context,
 					metav1.ObjectMeta,
 					kargoapi.ArgoCDAppUpdate,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 				) error {
 					return errors.New("something went wrong")
 				},
@@ -84,8 +84,8 @@ func TestArgoCDPromote(t *testing.T) {
 			},
 			assertions: func(
 				newStatus *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -104,7 +104,7 @@ func TestArgoCDPromote(t *testing.T) {
 					context.Context,
 					metav1.ObjectMeta,
 					kargoapi.ArgoCDAppUpdate,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 				) error {
 					return nil
 				},
@@ -120,8 +120,8 @@ func TestArgoCDPromote(t *testing.T) {
 			},
 			assertions: func(
 				newStatus *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -232,7 +232,7 @@ func TestArgoCDDoSingleUpdate(t *testing.T) {
 				},
 				applyArgoCDSourceUpdateFn: func(
 					argocd.ApplicationSource,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					kargoapi.ArgoCDSourceUpdate,
 				) (argocd.ApplicationSource, error) {
 					return argocd.ApplicationSource{}, errors.New("something went wrong")
@@ -282,7 +282,7 @@ func TestArgoCDDoSingleUpdate(t *testing.T) {
 				},
 				applyArgoCDSourceUpdateFn: func(
 					argocd.ApplicationSource,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					kargoapi.ArgoCDSourceUpdate,
 				) (argocd.ApplicationSource, error) {
 					return argocd.ApplicationSource{}, errors.New("something went wrong")
@@ -391,7 +391,7 @@ func TestArgoCDDoSingleUpdate(t *testing.T) {
 					context.Background(),
 					testCase.stageMeta,
 					testCase.update,
-					kargoapi.SimpleFreight{},
+					kargoapi.FreightReference{},
 				),
 			)
 		})
@@ -519,7 +519,7 @@ func TestApplyArgoCDSourceUpdate(t *testing.T) {
 	testCases := []struct {
 		name       string
 		source     argocd.ApplicationSource
-		newFreight kargoapi.SimpleFreight
+		newFreight kargoapi.FreightReference
 		update     kargoapi.ArgoCDSourceUpdate
 		assertions func(
 			originalSource argocd.ApplicationSource,
@@ -551,7 +551,7 @@ func TestApplyArgoCDSourceUpdate(t *testing.T) {
 			source: argocd.ApplicationSource{
 				RepoURL: "fake-url",
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{
 					{
 						RepoURL: "fake-url",
@@ -583,7 +583,7 @@ func TestApplyArgoCDSourceUpdate(t *testing.T) {
 				RepoURL: "fake-url",
 				Chart:   "fake-chart",
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Charts: []kargoapi.Chart{
 					{
 						RegistryURL: "fake-url",
@@ -616,7 +616,7 @@ func TestApplyArgoCDSourceUpdate(t *testing.T) {
 			source: argocd.ApplicationSource{
 				RepoURL: "fake-url",
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Images: []kargoapi.Image{
 					{
 						RepoURL: "fake-image-url",
@@ -666,7 +666,7 @@ func TestApplyArgoCDSourceUpdate(t *testing.T) {
 			source: argocd.ApplicationSource{
 				RepoURL: "fake-url",
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Images: []kargoapi.Image{
 					{
 						RepoURL: "fake-image-url",
diff --git a/internal/controller/promotion/composite_test.go b/internal/controller/promotion/composite_test.go
index cbf10486..75a56ad3 100644
--- a/internal/controller/promotion/composite_test.go
+++ b/internal/controller/promotion/composite_test.go
@@ -41,11 +41,11 @@ func TestCompositePromote(t *testing.T) {
 	testCases := []struct {
 		name       string
 		promoMech  *compositeMechanism
-		newFreight kargoapi.SimpleFreight
+		newFreight kargoapi.FreightReference
 		assertions func(
 			promoStatus *kargoapi.PromotionStatus,
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -58,10 +58,10 @@ func TestCompositePromote(t *testing.T) {
 						PromoteFn: func(
 							context.Context,
 							*kargoapi.Stage,
-							kargoapi.SimpleFreight,
-						) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+							kargoapi.FreightReference,
+						) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 							return &kargoapi.PromotionStatus{},
-								kargoapi.SimpleFreight{},
+								kargoapi.FreightReference{},
 								errors.New("something went wrong")
 						},
 					},
@@ -69,8 +69,8 @@ func TestCompositePromote(t *testing.T) {
 			},
 			assertions: func(
 				promoStatus *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -91,8 +91,8 @@ func TestCompositePromote(t *testing.T) {
 						PromoteFn: func(
 							_ context.Context,
 							_ *kargoapi.Stage,
-							newFreight kargoapi.SimpleFreight,
-						) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+							newFreight kargoapi.FreightReference,
+						) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 							// This is not a realistic change that a child promotion mechanism
 							// would make, but for testing purposes, this is good enough to
 							// help us assert that the function under test does return all
@@ -105,8 +105,8 @@ func TestCompositePromote(t *testing.T) {
 			},
 			assertions: func(
 				promoStatus *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
diff --git a/internal/controller/promotion/git_test.go b/internal/controller/promotion/git_test.go
index 9d6ee8b8..c82b91ed 100644
--- a/internal/controller/promotion/git_test.go
+++ b/internal/controller/promotion/git_test.go
@@ -26,7 +26,7 @@ func TestNewGitMechanism(t *testing.T) {
 		},
 		func(
 			update kargoapi.GitRepoUpdate,
-			newFreight kargoapi.SimpleFreight,
+			newFreight kargoapi.FreightReference,
 			homeDir string,
 			workingDir string,
 		) ([]string, error) {
@@ -56,8 +56,8 @@ func TestGitPromote(t *testing.T) {
 		promoMech  *gitMechanism
 		assertions func(
 			status *kargoapi.PromotionStatus,
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -70,8 +70,8 @@ func TestGitPromote(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -88,15 +88,15 @@ func TestGitPromote(t *testing.T) {
 					_ context.Context,
 					_ *kargoapi.Promotion,
 					_ kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
-				) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+					newFreight kargoapi.FreightReference,
+				) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 					return nil, newFreight, errors.New("something went wrong")
 				},
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -114,15 +114,15 @@ func TestGitPromote(t *testing.T) {
 					_ context.Context,
 					_ *kargoapi.Promotion,
 					_ kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
-				) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+					newFreight kargoapi.FreightReference,
+				) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 					return &kargoapi.PromotionStatus{Phase: kargoapi.PromotionPhaseSucceeded}, newFreight, nil
 				},
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -132,7 +132,7 @@ func TestGitPromote(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			newFreightIn := kargoapi.SimpleFreight{}
+			newFreightIn := kargoapi.FreightReference{}
 			status, newFreightOut, err := testCase.promoMech.Promote(
 				context.Background(),
 				&kargoapi.Stage{
@@ -155,8 +155,8 @@ func TestGitDoSingleUpdate(t *testing.T) {
 		promoMech  *gitMechanism
 		assertions func(
 			status *kargoapi.PromotionStatus,
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -172,8 +172,8 @@ func TestGitDoSingleUpdate(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -200,8 +200,8 @@ func TestGitDoSingleUpdate(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -227,7 +227,7 @@ func TestGitDoSingleUpdate(t *testing.T) {
 				},
 				gitCommitFn: func(
 					update kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
+					newFreight kargoapi.FreightReference,
 					readRef string,
 					writeBranch string,
 					repo git.Repo,
@@ -237,8 +237,8 @@ func TestGitDoSingleUpdate(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -264,7 +264,7 @@ func TestGitDoSingleUpdate(t *testing.T) {
 				},
 				gitCommitFn: func(
 					update kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
+					newFreight kargoapi.FreightReference,
 					readRef string,
 					writeBranch string,
 					repo git.Repo,
@@ -274,8 +274,8 @@ func TestGitDoSingleUpdate(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -292,7 +292,7 @@ func TestGitDoSingleUpdate(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			newFreightIn := kargoapi.SimpleFreight{
+			newFreightIn := kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{{}},
 			}
 			status, newFreightOut, err := testCase.promoMech.doSingleUpdate(
diff --git a/internal/controller/promotion/helm_test.go b/internal/controller/promotion/helm_test.go
index 358346de..6efd54d1 100644
--- a/internal/controller/promotion/helm_test.go
+++ b/internal/controller/promotion/helm_test.go
@@ -252,7 +252,7 @@ func TestHelmerApply(t *testing.T) {
 					kargoapi.GitRepoUpdate{
 						Helm: &kargoapi.HelmPromotionMechanism{},
 					},
-					kargoapi.SimpleFreight{}, // The way the tests are structured, this value doesn't matter
+					kargoapi.FreightReference{}, // The way the tests are structured, this value doesn't matter
 					"",
 					"",
 				),
diff --git a/internal/controller/promotion/kustomize_test.go b/internal/controller/promotion/kustomize_test.go
index 5eb9b440..503d8ac5 100644
--- a/internal/controller/promotion/kustomize_test.go
+++ b/internal/controller/promotion/kustomize_test.go
@@ -158,7 +158,7 @@ func TestKustomizerApply(t *testing.T) {
 			testCase.assertions(
 				testCase.kustomizer.apply(
 					testCase.update,
-					kargoapi.SimpleFreight{
+					kargoapi.FreightReference{
 						Images: []kargoapi.Image{
 							{
 								RepoURL: "fake-image",
diff --git a/internal/controller/promotion/render_test.go b/internal/controller/promotion/render_test.go
index 06d6fbde..efd69cea 100644
--- a/internal/controller/promotion/render_test.go
+++ b/internal/controller/promotion/render_test.go
@@ -32,11 +32,11 @@ func TestKargoRenderPromote(t *testing.T) {
 		name       string
 		promoMech  *kargoRenderMechanism
 		stage      *kargoapi.Stage
-		newFreight kargoapi.SimpleFreight
+		newFreight kargoapi.FreightReference
 		assertions func(
 			status *kargoapi.PromotionStatus,
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -50,8 +50,8 @@ func TestKargoRenderPromote(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -65,8 +65,8 @@ func TestKargoRenderPromote(t *testing.T) {
 					_ context.Context,
 					_ *kargoapi.Promotion,
 					_ kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
-				) (kargoapi.SimpleFreight, error) {
+					newFreight kargoapi.FreightReference,
+				) (kargoapi.FreightReference, error) {
 					return newFreight, errors.New("something went wrong")
 				},
 			},
@@ -81,7 +81,7 @@ func TestKargoRenderPromote(t *testing.T) {
 					},
 				},
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Images: []kargoapi.Image{
 					{
 						RepoURL: "fake-url",
@@ -91,8 +91,8 @@ func TestKargoRenderPromote(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -107,8 +107,8 @@ func TestKargoRenderPromote(t *testing.T) {
 					_ context.Context,
 					_ *kargoapi.Promotion,
 					_ kargoapi.GitRepoUpdate,
-					newFreight kargoapi.SimpleFreight,
-				) (kargoapi.SimpleFreight, error) {
+					newFreight kargoapi.FreightReference,
+				) (kargoapi.FreightReference, error) {
 					return newFreight, nil
 				},
 			},
@@ -123,7 +123,7 @@ func TestKargoRenderPromote(t *testing.T) {
 					},
 				},
 			},
-			newFreight: kargoapi.SimpleFreight{
+			newFreight: kargoapi.FreightReference{
 				Images: []kargoapi.Image{
 					{
 						RepoURL: "fake-url",
@@ -133,8 +133,8 @@ func TestKargoRenderPromote(t *testing.T) {
 			},
 			assertions: func(
 				status *kargoapi.PromotionStatus,
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -159,12 +159,12 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 	const testRef = "fake-ref"
 	testCases := []struct {
 		name       string
-		freight    kargoapi.SimpleFreight
+		freight    kargoapi.FreightReference
 		promoMech  *kargoRenderMechanism
 		update     kargoapi.GitRepoUpdate
 		assertions func(
-			newFreightIn kargoapi.SimpleFreight,
-			newFreightOut kargoapi.SimpleFreight,
+			newFreightIn kargoapi.FreightReference,
+			newFreightOut kargoapi.FreightReference,
 			err error,
 		)
 	}{
@@ -179,8 +179,8 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 				},
 			},
 			assertions: func(
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -209,8 +209,8 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 				},
 			},
 			assertions: func(
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -252,8 +252,8 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 				},
 			},
 			assertions: func(
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.Error(t, err)
@@ -268,7 +268,7 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 		},
 		{
 			name: "success -- all images -- no action",
-			freight: kargoapi.SimpleFreight{
+			freight: kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{{}},
 				Images: []kargoapi.Image{
 					{
@@ -324,8 +324,8 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 				},
 			},
 			assertions: func(
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
@@ -341,7 +341,7 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 		},
 		{
 			name: "success -- some images -- commit",
-			freight: kargoapi.SimpleFreight{
+			freight: kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{{}},
 				Images: []kargoapi.Image{
 					{
@@ -409,8 +409,8 @@ func TestKargoRenderDoSingleUpdate(t *testing.T) {
 				},
 			},
 			assertions: func(
-				newFreightIn kargoapi.SimpleFreight,
-				newFreightOut kargoapi.SimpleFreight,
+				newFreightIn kargoapi.FreightReference,
+				newFreightOut kargoapi.FreightReference,
 				err error,
 			) {
 				require.NoError(t, err)
diff --git a/internal/controller/promotion/root_test.go b/internal/controller/promotion/root_test.go
index 258ab391..f037498a 100644
--- a/internal/controller/promotion/root_test.go
+++ b/internal/controller/promotion/root_test.go
@@ -26,8 +26,8 @@ type FakeMechanism struct {
 	PromoteFn func(
 		context.Context,
 		*kargoapi.Stage,
-		kargoapi.SimpleFreight,
-	) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error)
+		kargoapi.FreightReference,
+	) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error)
 }
 
 // GetName implements the Mechanism interface.
@@ -40,7 +40,7 @@ func (f *FakeMechanism) Promote(
 	ctx context.Context,
 	stage *kargoapi.Stage,
 	_ *kargoapi.Promotion,
-	freight kargoapi.SimpleFreight,
-) (*kargoapi.PromotionStatus, kargoapi.SimpleFreight, error) {
+	freight kargoapi.FreightReference,
+) (*kargoapi.PromotionStatus, kargoapi.FreightReference, error) {
 	return f.PromoteFn(ctx, stage, freight)
 }
diff --git a/internal/controller/stages/health_test.go b/internal/controller/stages/health_test.go
index dc90b7eb..de451f1a 100644
--- a/internal/controller/stages/health_test.go
+++ b/internal/controller/stages/health_test.go
@@ -15,7 +15,7 @@ import (
 func TestCheckHealth(t *testing.T) {
 	testCases := []struct {
 		name             string
-		freight          kargoapi.SimpleFreight
+		freight          kargoapi.FreightReference
 		argoCDAppUpdates []kargoapi.ArgoCDAppUpdate
 		getArgoCDAppFn   func(
 			context.Context,
@@ -234,7 +234,7 @@ func TestCheckHealth(t *testing.T) {
 
 		{
 			name: "Argo CD App not synced",
-			freight: kargoapi.SimpleFreight{
+			freight: kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{
 					{
 						RepoURL: "fake-url",
@@ -297,7 +297,7 @@ func TestCheckHealth(t *testing.T) {
 
 		{
 			name: "Argo CD App healthy and synced",
-			freight: kargoapi.SimpleFreight{
+			freight: kargoapi.FreightReference{
 				Commits: []kargoapi.GitCommit{
 					{
 						RepoURL: "fake-url",
diff --git a/internal/controller/stages/stages_test.go b/internal/controller/stages/stages_test.go
index 49150701..e7608e53 100644
--- a/internal/controller/stages/stages_test.go
+++ b/internal/controller/stages/stages_test.go
@@ -196,7 +196,7 @@ func TestSyncControlFlowStage(t *testing.T) {
 				},
 				Status: kargoapi.StageStatus{
 					Phase:          kargoapi.StagePhaseNotApplicable,
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 					Health:         &kargoapi.Health{},
 				},
 			},
@@ -317,14 +317,14 @@ func TestSyncNormalStage(t *testing.T) {
 				},
 				Status: kargoapi.StageStatus{
 					Phase:          kargoapi.StagePhaseVerifying,
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -358,7 +358,7 @@ func TestSyncNormalStage(t *testing.T) {
 				},
 				Status: kargoapi.StageStatus{
 					Phase: kargoapi.StagePhaseVerifying,
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{},
 					},
 				},
@@ -367,7 +367,7 @@ func TestSyncNormalStage(t *testing.T) {
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -397,14 +397,14 @@ func TestSyncNormalStage(t *testing.T) {
 				},
 				Status: kargoapi.StageStatus{
 					Phase:          kargoapi.StagePhaseVerifying,
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -440,14 +440,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -490,14 +490,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -534,14 +534,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -591,14 +591,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -642,7 +642,7 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-freight-id",
 					},
 				},
@@ -651,7 +651,7 @@ func TestSyncNormalStage(t *testing.T) {
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -699,14 +699,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -764,14 +764,14 @@ func TestSyncNormalStage(t *testing.T) {
 					PromotionMechanisms: &kargoapi.PromotionMechanisms{},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{},
+					CurrentFreight: &kargoapi.FreightReference{},
 				},
 			},
 			reconciler: &reconciler{
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return nil
@@ -843,7 +843,7 @@ func TestSyncNormalStage(t *testing.T) {
 				Status: kargoapi.StageStatus{
 					Phase:            kargoapi.StagePhaseVerifying,
 					CurrentPromotion: &kargoapi.PromotionInfo{},
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{},
 					},
 				},
@@ -852,7 +852,7 @@ func TestSyncNormalStage(t *testing.T) {
 				hasNonTerminalPromotionsFn: noNonTerminalPromotionsFn,
 				checkHealthFn: func(
 					context.Context,
-					kargoapi.SimpleFreight,
+					kargoapi.FreightReference,
 					[]kargoapi.ArgoCDAppUpdate,
 				) *kargoapi.Health {
 					return &kargoapi.Health{
diff --git a/internal/controller/stages/verification_test.go b/internal/controller/stages/verification_test.go
index 40742529..7a8c327e 100644
--- a/internal/controller/stages/verification_test.go
+++ b/internal/controller/stages/verification_test.go
@@ -27,7 +27,7 @@ func TestStarVerification(t *testing.T) {
 			name: "error listing AnalysisRuns",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -51,7 +51,7 @@ func TestStarVerification(t *testing.T) {
 			name: "Analysis run already exists",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -81,7 +81,7 @@ func TestStarVerification(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -117,7 +117,7 @@ func TestStarVerification(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -153,7 +153,7 @@ func TestStarVerification(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -195,7 +195,7 @@ func TestStarVerification(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -244,7 +244,7 @@ func TestStarVerification(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						ID: "fake-id",
 					},
 				},
@@ -321,7 +321,7 @@ func TestGetVerificationInfo(t *testing.T) {
 			name: "error getting AnalysisRun",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{
 							AnalysisRun: kargoapi.AnalysisRunReference{
 								Name:      "fake-run",
@@ -350,7 +350,7 @@ func TestGetVerificationInfo(t *testing.T) {
 			name: "AnalysisRun not found",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{
 							AnalysisRun: kargoapi.AnalysisRunReference{
 								Name:      "fake-run",
@@ -379,7 +379,7 @@ func TestGetVerificationInfo(t *testing.T) {
 			name: "success",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{
 							AnalysisRun: kargoapi.AnalysisRunReference{
 								Name:      "fake-run",
diff --git a/internal/controller/warehouses/git_test.go b/internal/controller/warehouses/git_test.go
index 0cc94e13..96c60493 100644
--- a/internal/controller/warehouses/git_test.go
+++ b/internal/controller/warehouses/git_test.go
@@ -15,27 +15,23 @@ import (
 
 func TestGetLatestCommits(t *testing.T) {
 	testCases := []struct {
-		name                  string
-		credentialsDB         credentials.Database
-		getLatestCommitMetaFn func(
-			context.Context,
-			string,
-			string,
-			*git.RepoCredentials,
-		) (*gitMeta, error)
+		name       string
+		reconciler *reconciler
 		assertions func(commits []kargoapi.GitCommit, err error)
 	}{
 		{
 			name: "error getting repo credentials",
-			credentialsDB: &credentials.FakeDB{
-				GetFn: func(
-					context.Context,
-					string,
-					credentials.Type,
-					string,
-				) (credentials.Credentials, bool, error) {
-					return credentials.Credentials{}, false,
-						errors.New("something went wrong")
+			reconciler: &reconciler{
+				credentialsDB: &credentials.FakeDB{
+					GetFn: func(
+						context.Context,
+						string,
+						credentials.Type,
+						string,
+					) (credentials.Credentials, bool, error) {
+						return credentials.Credentials{}, false,
+							errors.New("something went wrong")
+					},
 				},
 			},
 			assertions: func(commits []kargoapi.GitCommit, err error) {
@@ -52,24 +48,25 @@ func TestGetLatestCommits(t *testing.T) {
 
 		{
 			name: "error getting last commit ID",
-			credentialsDB: &credentials.FakeDB{
-				GetFn: func(
+			reconciler: &reconciler{
+				credentialsDB: &credentials.FakeDB{
+					GetFn: func(
+						context.Context,
+						string,
+						credentials.Type,
+						string,
+					) (credentials.Credentials, bool, error) {
+						return credentials.Credentials{}, false, nil
+					},
+				},
+				getLatestCommitMetaFn: func(
 					context.Context,
-					string,
-					credentials.Type,
-					string,
-				) (credentials.Credentials, bool, error) {
-					return credentials.Credentials{}, false, nil
+					kargoapi.GitSubscription,
+					*git.RepoCredentials,
+				) (*gitMeta, error) {
+					return nil, errors.New("something went wrong")
 				},
 			},
-			getLatestCommitMetaFn: func(
-				context.Context,
-				string,
-				string,
-				*git.RepoCredentials,
-			) (*gitMeta, error) {
-				return nil, errors.New("something went wrong")
-			},
 			assertions: func(commits []kargoapi.GitCommit, err error) {
 				require.Error(t, err)
 				require.Contains(
@@ -84,24 +81,25 @@ func TestGetLatestCommits(t *testing.T) {
 
 		{
 			name: "success",
-			credentialsDB: &credentials.FakeDB{
-				GetFn: func(
+			reconciler: &reconciler{
+				credentialsDB: &credentials.FakeDB{
+					GetFn: func(
+						context.Context,
+						string,
+						credentials.Type,
+						string,
+					) (credentials.Credentials, bool, error) {
+						return credentials.Credentials{}, false, nil
+					},
+				},
+				getLatestCommitMetaFn: func(
 					context.Context,
-					string,
-					credentials.Type,
-					string,
-				) (credentials.Credentials, bool, error) {
-					return credentials.Credentials{}, false, nil
+					kargoapi.GitSubscription,
+					*git.RepoCredentials,
+				) (*gitMeta, error) {
+					return &gitMeta{Commit: "fake-commit", Message: "message"}, nil
 				},
 			},
-			getLatestCommitMetaFn: func(
-				context.Context,
-				string,
-				string,
-				*git.RepoCredentials,
-			) (*gitMeta, error) {
-				return &gitMeta{Commit: "fake-commit", Message: "message"}, nil
-			},
 			assertions: func(commits []kargoapi.GitCommit, err error) {
 				require.NoError(t, err)
 				require.Len(t, commits, 1)
@@ -119,12 +117,8 @@ func TestGetLatestCommits(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			r := reconciler{
-				credentialsDB:         testCase.credentialsDB,
-				getLatestCommitMetaFn: testCase.getLatestCommitMetaFn,
-			}
 			testCase.assertions(
-				r.getLatestCommits(
+				testCase.reconciler.getLatestCommits(
 					context.Background(),
 					"fake-namespace",
 					[]kargoapi.RepoSubscription{
@@ -140,24 +134,27 @@ func TestGetLatestCommits(t *testing.T) {
 	}
 }
 
-func TestGetLatestCommitID(t *testing.T) {
+func TestGetLatestCommitMeta(t *testing.T) {
 	testCases := []struct {
 		name       string
-		repoURL    string
-		branch     string
+		sub        kargoapi.GitSubscription
 		assertions func(*gitMeta, error)
 	}{
 		{
-			name:    "error cloning repo",
-			repoURL: "fake-url", // This should force a failure
+			name: "error cloning repo",
+			sub: kargoapi.GitSubscription{
+				RepoURL: "fake-url", // This should force a failure
+			},
 			assertions: func(_ *gitMeta, err error) {
 				require.Error(t, err)
 				require.Contains(t, err.Error(), "error cloning git repo")
 			},
 		},
 		{
-			name:    "success",
-			repoURL: "https://github.com/akuity/kargo.git",
+			name: "success",
+			sub: kargoapi.GitSubscription{
+				RepoURL: "https://github.com/akuity/kargo.git",
+			},
 			assertions: func(gm *gitMeta, err error) {
 				require.NoError(t, err)
 				require.NotEmpty(t, gm.Commit)
@@ -169,7 +166,7 @@ func TestGetLatestCommitID(t *testing.T) {
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
 			testCase.assertions(
-				getLatestCommitMeta(context.TODO(), testCase.repoURL, testCase.branch, nil),
+				getLatestCommitMeta(context.TODO(), testCase.sub, nil),
 			)
 		})
 	}
diff --git a/internal/credentials/credentials_test.go b/internal/credentials/credentials_test.go
index 2e2bafe6..b86d80eb 100644
--- a/internal/credentials/credentials_test.go
+++ b/internal/credentials/credentials_test.go
@@ -22,7 +22,7 @@ func TestNewKubernetesDatabase(t *testing.T) {
 	require.NotNil(t, d)
 	k, ok := d.(*kubernetesDatabase)
 	require.True(t, ok)
-	require.Equal(t, testArgoCDNameSpace, k.argoCDNamespace)
+	require.Equal(t, testArgoCDNameSpace, k.ArgoCDNamespace)
 	require.Same(t, testClient, k.kargoClient)
 	require.Same(t, testClient, k.argoClient)
 }
diff --git a/internal/kubeclient/indexer_test.go b/internal/kubeclient/indexer_test.go
index eb506218..e5941aef 100644
--- a/internal/kubeclient/indexer_test.go
+++ b/internal/kubeclient/indexer_test.go
@@ -42,7 +42,7 @@ func TestIndexStagesByAnalysisRun(t *testing.T) {
 					},
 				},
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{
 							AnalysisRun: kargoapi.AnalysisRunReference{
 								Namespace: "fake-namespace",
@@ -75,7 +75,7 @@ func TestIndexStagesByAnalysisRun(t *testing.T) {
 			controllerShardName: "",
 			stage: &kargoapi.Stage{
 				Status: kargoapi.StageStatus{
-					CurrentFreight: &kargoapi.SimpleFreight{
+					CurrentFreight: &kargoapi.FreightReference{
 						VerificationInfo: &kargoapi.VerificationInfo{
 							AnalysisRun: kargoapi.AnalysisRunReference{
 								Namespace: "fake-namespace",
diff --git a/internal/webhook/freight/webhook_test.go b/internal/webhook/freight/webhook_test.go
index 72b70a3f..691e4ffc 100644
--- a/internal/webhook/freight/webhook_test.go
+++ b/internal/webhook/freight/webhook_test.go
@@ -373,7 +373,7 @@ func TestValidateDelete(t *testing.T) {
 								Name: "fake-stage",
 							},
 							Status: kargoapi.StageStatus{
-								CurrentFreight: &kargoapi.SimpleFreight{
+								CurrentFreight: &kargoapi.FreightReference{
 									ID: "fake-id",
 								},
 							},