Repository: akuity/kargo

Issue:
UI: Settings redesign
Since we designed a new settings view for projects, we should follow this new design and improve all other settings views.

![Image](https://github.com/user-attachments/assets/417055ae-59ea-4a9a-97c6-87bf96491751)

# Suggested Implementation
Implement a similar design to this:

![Image](https://github.com/user-attachments/assets/afdf4fed-459f-4fe3-a2ca-52a86d498616)

Discussion:

Solution patch:
diff --git a/api/v1alpha1/expression_types.go b/api/v1alpha1/expression_types.go
new file mode 100644
index 00000000..02e739a9
--- /dev/null
+++ b/api/v1alpha1/expression_types.go
@@ -0,0 +1,19 @@
+package v1alpha1
+
+// ExpressionVariable describes a single variable that may be referenced by
+// expressions in the context of a ClusterPromotionTask, PromotionTask,
+// Promotion, AnalysisRun arguments, or other objects that support expressions.
+//
+// It is used to pass information to the expression evaluation engine, and to
+// allow for dynamic evaluation of expressions based on the variable values.
+type ExpressionVariable struct {
+	// Name is the name of the variable.
+	//
+	// +kubebuilder:validation:MinLength=1
+	// +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
+	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
+	// Value is the value of the variable. It is allowed to utilize expressions
+	// in the value.
+	// See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
+	Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
+}
diff --git a/api/v1alpha1/generated.pb.go b/api/v1alpha1/generated.pb.go
index 318a4af1..4a85c659 100644
--- a/api/v1alpha1/generated.pb.go
+++ b/api/v1alpha1/generated.pb.go
@@ -506,10 +506,38 @@ func (m *DiscoveredImageReference) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_DiscoveredImageReference proto.InternalMessageInfo
 
+func (m *ExpressionVariable) Reset()      { *m = ExpressionVariable{} }
+func (*ExpressionVariable) ProtoMessage() {}
+func (*ExpressionVariable) Descriptor() ([]byte, []int) {
+	return fileDescriptor_e26b7f7bbc391025, []int{17}
+}
+func (m *ExpressionVariable) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ExpressionVariable) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *ExpressionVariable) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ExpressionVariable.Merge(m, src)
+}
+func (m *ExpressionVariable) XXX_Size() int {
+	return m.Size()
+}
+func (m *ExpressionVariable) XXX_DiscardUnknown() {
+	xxx_messageInfo_ExpressionVariable.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ExpressionVariable proto.InternalMessageInfo
+
 func (m *Freight) Reset()      { *m = Freight{} }
 func (*Freight) ProtoMessage() {}
 func (*Freight) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{17}
+	return fileDescriptor_e26b7f7bbc391025, []int{18}
 }
 func (m *Freight) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -537,7 +565,7 @@ var xxx_messageInfo_Freight proto.InternalMessageInfo
 func (m *FreightCollection) Reset()      { *m = FreightCollection{} }
 func (*FreightCollection) ProtoMessage() {}
 func (*FreightCollection) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{18}
+	return fileDescriptor_e26b7f7bbc391025, []int{19}
 }
 func (m *FreightCollection) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -565,7 +593,7 @@ var xxx_messageInfo_FreightCollection proto.InternalMessageInfo
 func (m *FreightList) Reset()      { *m = FreightList{} }
 func (*FreightList) ProtoMessage() {}
 func (*FreightList) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{19}
+	return fileDescriptor_e26b7f7bbc391025, []int{20}
 }
 func (m *FreightList) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -593,7 +621,7 @@ var xxx_messageInfo_FreightList proto.InternalMessageInfo
 func (m *FreightOrigin) Reset()      { *m = FreightOrigin{} }
 func (*FreightOrigin) ProtoMessage() {}
 func (*FreightOrigin) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{20}
+	return fileDescriptor_e26b7f7bbc391025, []int{21}
 }
 func (m *FreightOrigin) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -621,7 +649,7 @@ var xxx_messageInfo_FreightOrigin proto.InternalMessageInfo
 func (m *FreightReference) Reset()      { *m = FreightReference{} }
 func (*FreightReference) ProtoMessage() {}
 func (*FreightReference) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{21}
+	return fileDescriptor_e26b7f7bbc391025, []int{22}
 }
 func (m *FreightReference) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -649,7 +677,7 @@ var xxx_messageInfo_FreightReference proto.InternalMessageInfo
 func (m *FreightRequest) Reset()      { *m = FreightRequest{} }
 func (*FreightRequest) ProtoMessage() {}
 func (*FreightRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{22}
+	return fileDescriptor_e26b7f7bbc391025, []int{23}
 }
 func (m *FreightRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -677,7 +705,7 @@ var xxx_messageInfo_FreightRequest proto.InternalMessageInfo
 func (m *FreightSources) Reset()      { *m = FreightSources{} }
 func (*FreightSources) ProtoMessage() {}
 func (*FreightSources) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{23}
+	return fileDescriptor_e26b7f7bbc391025, []int{24}
 }
 func (m *FreightSources) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -705,7 +733,7 @@ var xxx_messageInfo_FreightSources proto.InternalMessageInfo
 func (m *FreightStatus) Reset()      { *m = FreightStatus{} }
 func (*FreightStatus) ProtoMessage() {}
 func (*FreightStatus) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{24}
+	return fileDescriptor_e26b7f7bbc391025, []int{25}
 }
 func (m *FreightStatus) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -733,7 +761,7 @@ var xxx_messageInfo_FreightStatus proto.InternalMessageInfo
 func (m *GitCommit) Reset()      { *m = GitCommit{} }
 func (*GitCommit) ProtoMessage() {}
 func (*GitCommit) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{25}
+	return fileDescriptor_e26b7f7bbc391025, []int{26}
 }
 func (m *GitCommit) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -761,7 +789,7 @@ var xxx_messageInfo_GitCommit proto.InternalMessageInfo
 func (m *GitDiscoveryResult) Reset()      { *m = GitDiscoveryResult{} }
 func (*GitDiscoveryResult) ProtoMessage() {}
 func (*GitDiscoveryResult) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{26}
+	return fileDescriptor_e26b7f7bbc391025, []int{27}
 }
 func (m *GitDiscoveryResult) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -789,7 +817,7 @@ var xxx_messageInfo_GitDiscoveryResult proto.InternalMessageInfo
 func (m *GitSubscription) Reset()      { *m = GitSubscription{} }
 func (*GitSubscription) ProtoMessage() {}
 func (*GitSubscription) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{27}
+	return fileDescriptor_e26b7f7bbc391025, []int{28}
 }
 func (m *GitSubscription) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -817,7 +845,7 @@ var xxx_messageInfo_GitSubscription proto.InternalMessageInfo
 func (m *Health) Reset()      { *m = Health{} }
 func (*Health) ProtoMessage() {}
 func (*Health) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{28}
+	return fileDescriptor_e26b7f7bbc391025, []int{29}
 }
 func (m *Health) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -845,7 +873,7 @@ var xxx_messageInfo_Health proto.InternalMessageInfo
 func (m *HealthCheckStep) Reset()      { *m = HealthCheckStep{} }
 func (*HealthCheckStep) ProtoMessage() {}
 func (*HealthCheckStep) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{29}
+	return fileDescriptor_e26b7f7bbc391025, []int{30}
 }
 func (m *HealthCheckStep) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -873,7 +901,7 @@ var xxx_messageInfo_HealthCheckStep proto.InternalMessageInfo
 func (m *Image) Reset()      { *m = Image{} }
 func (*Image) ProtoMessage() {}
 func (*Image) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{30}
+	return fileDescriptor_e26b7f7bbc391025, []int{31}
 }
 func (m *Image) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -901,7 +929,7 @@ var xxx_messageInfo_Image proto.InternalMessageInfo
 func (m *ImageDiscoveryResult) Reset()      { *m = ImageDiscoveryResult{} }
 func (*ImageDiscoveryResult) ProtoMessage() {}
 func (*ImageDiscoveryResult) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{31}
+	return fileDescriptor_e26b7f7bbc391025, []int{32}
 }
 func (m *ImageDiscoveryResult) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -929,7 +957,7 @@ var xxx_messageInfo_ImageDiscoveryResult proto.InternalMessageInfo
 func (m *ImageSubscription) Reset()      { *m = ImageSubscription{} }
 func (*ImageSubscription) ProtoMessage() {}
 func (*ImageSubscription) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{32}
+	return fileDescriptor_e26b7f7bbc391025, []int{33}
 }
 func (m *ImageSubscription) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -957,7 +985,7 @@ var xxx_messageInfo_ImageSubscription proto.InternalMessageInfo
 func (m *Project) Reset()      { *m = Project{} }
 func (*Project) ProtoMessage() {}
 func (*Project) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{33}
+	return fileDescriptor_e26b7f7bbc391025, []int{34}
 }
 func (m *Project) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -985,7 +1013,7 @@ var xxx_messageInfo_Project proto.InternalMessageInfo
 func (m *ProjectList) Reset()      { *m = ProjectList{} }
 func (*ProjectList) ProtoMessage() {}
 func (*ProjectList) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{34}
+	return fileDescriptor_e26b7f7bbc391025, []int{35}
 }
 func (m *ProjectList) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1013,7 +1041,7 @@ var xxx_messageInfo_ProjectList proto.InternalMessageInfo
 func (m *ProjectSpec) Reset()      { *m = ProjectSpec{} }
 func (*ProjectSpec) ProtoMessage() {}
 func (*ProjectSpec) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{35}
+	return fileDescriptor_e26b7f7bbc391025, []int{36}
 }
 func (m *ProjectSpec) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1041,7 +1069,7 @@ var xxx_messageInfo_ProjectSpec proto.InternalMessageInfo
 func (m *ProjectStatus) Reset()      { *m = ProjectStatus{} }
 func (*ProjectStatus) ProtoMessage() {}
 func (*ProjectStatus) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{36}
+	return fileDescriptor_e26b7f7bbc391025, []int{37}
 }
 func (m *ProjectStatus) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1069,7 +1097,7 @@ var xxx_messageInfo_ProjectStatus proto.InternalMessageInfo
 func (m *Promotion) Reset()      { *m = Promotion{} }
 func (*Promotion) ProtoMessage() {}
 func (*Promotion) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{37}
+	return fileDescriptor_e26b7f7bbc391025, []int{38}
 }
 func (m *Promotion) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1097,7 +1125,7 @@ var xxx_messageInfo_Promotion proto.InternalMessageInfo
 func (m *PromotionList) Reset()      { *m = PromotionList{} }
 func (*PromotionList) ProtoMessage() {}
 func (*PromotionList) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{38}
+	return fileDescriptor_e26b7f7bbc391025, []int{39}
 }
 func (m *PromotionList) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1125,7 +1153,7 @@ var xxx_messageInfo_PromotionList proto.InternalMessageInfo
 func (m *PromotionPolicy) Reset()      { *m = PromotionPolicy{} }
 func (*PromotionPolicy) ProtoMessage() {}
 func (*PromotionPolicy) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{39}
+	return fileDescriptor_e26b7f7bbc391025, []int{40}
 }
 func (m *PromotionPolicy) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1153,7 +1181,7 @@ var xxx_messageInfo_PromotionPolicy proto.InternalMessageInfo
 func (m *PromotionReference) Reset()      { *m = PromotionReference{} }
 func (*PromotionReference) ProtoMessage() {}
 func (*PromotionReference) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{40}
+	return fileDescriptor_e26b7f7bbc391025, []int{41}
 }
 func (m *PromotionReference) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1181,7 +1209,7 @@ var xxx_messageInfo_PromotionReference proto.InternalMessageInfo
 func (m *PromotionSpec) Reset()      { *m = PromotionSpec{} }
 func (*PromotionSpec) ProtoMessage() {}
 func (*PromotionSpec) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{41}
+	return fileDescriptor_e26b7f7bbc391025, []int{42}
 }
 func (m *PromotionSpec) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1209,7 +1237,7 @@ var xxx_messageInfo_PromotionSpec proto.InternalMessageInfo
 func (m *PromotionStatus) Reset()      { *m = PromotionStatus{} }
 func (*PromotionStatus) ProtoMessage() {}
 func (*PromotionStatus) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{42}
+	return fileDescriptor_e26b7f7bbc391025, []int{43}
 }
 func (m *PromotionStatus) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1237,7 +1265,7 @@ var xxx_messageInfo_PromotionStatus proto.InternalMessageInfo
 func (m *PromotionStep) Reset()      { *m = PromotionStep{} }
 func (*PromotionStep) ProtoMessage() {}
 func (*PromotionStep) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{43}
+	return fileDescriptor_e26b7f7bbc391025, []int{44}
 }
 func (m *PromotionStep) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1265,7 +1293,7 @@ var xxx_messageInfo_PromotionStep proto.InternalMessageInfo
 func (m *PromotionStepRetry) Reset()      { *m = PromotionStepRetry{} }
 func (*PromotionStepRetry) ProtoMessage() {}
 func (*PromotionStepRetry) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{44}
+	return fileDescriptor_e26b7f7bbc391025, []int{45}
 }
 func (m *PromotionStepRetry) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1293,7 +1321,7 @@ var xxx_messageInfo_PromotionStepRetry proto.InternalMessageInfo
 func (m *PromotionTask) Reset()      { *m = PromotionTask{} }
 func (*PromotionTask) ProtoMessage() {}
 func (*PromotionTask) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{45}
+	return fileDescriptor_e26b7f7bbc391025, []int{46}
 }
 func (m *PromotionTask) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1321,7 +1349,7 @@ var xxx_messageInfo_PromotionTask proto.InternalMessageInfo
 func (m *PromotionTaskList) Reset()      { *m = PromotionTaskList{} }
 func (*PromotionTaskList) ProtoMessage() {}
 func (*PromotionTaskList) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{46}
+	return fileDescriptor_e26b7f7bbc391025, []int{47}
 }
 func (m *PromotionTaskList) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1349,7 +1377,7 @@ var xxx_messageInfo_PromotionTaskList proto.InternalMessageInfo
 func (m *PromotionTaskReference) Reset()      { *m = PromotionTaskReference{} }
 func (*PromotionTaskReference) ProtoMessage() {}
 func (*PromotionTaskReference) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{47}
+	return fileDescriptor_e26b7f7bbc391025, []int{48}
 }
 func (m *PromotionTaskReference) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1377,7 +1405,7 @@ var xxx_messageInfo_PromotionTaskReference proto.InternalMessageInfo
 func (m *PromotionTaskSpec) Reset()      { *m = PromotionTaskSpec{} }
 func (*PromotionTaskSpec) ProtoMessage() {}
 func (*PromotionTaskSpec) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{48}
+	return fileDescriptor_e26b7f7bbc391025, []int{49}
 }
 func (m *PromotionTaskSpec) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1405,7 +1433,7 @@ var xxx_messageInfo_PromotionTaskSpec proto.InternalMessageInfo
 func (m *PromotionTemplate) Reset()      { *m = PromotionTemplate{} }
 func (*PromotionTemplate) ProtoMessage() {}
 func (*PromotionTemplate) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{49}
+	return fileDescriptor_e26b7f7bbc391025, []int{50}
 }
 func (m *PromotionTemplate) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1433,7 +1461,7 @@ var xxx_messageInfo_PromotionTemplate proto.InternalMessageInfo
 func (m *PromotionTemplateSpec) Reset()      { *m = PromotionTemplateSpec{} }
 func (*PromotionTemplateSpec) ProtoMessage() {}
 func (*PromotionTemplateSpec) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{50}
+	return fileDescriptor_e26b7f7bbc391025, []int{51}
 }
 func (m *PromotionTemplateSpec) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -1458,34 +1486,6 @@ func (m *PromotionTemplateSpec) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_PromotionTemplateSpec proto.InternalMessageInfo
 
-func (m *PromotionVariable) Reset()      { *m = PromotionVariable{} }
-func (*PromotionVariable) ProtoMessage() {}
-func (*PromotionVariable) Descriptor() ([]byte, []int) {
-	return fileDescriptor_e26b7f7bbc391025, []int{51}
-}
-func (m *PromotionVariable) XXX_Unmarshal(b []byte) error {
-	return m.Unmarshal(b)
-}
-func (m *PromotionVariable) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	b = b[:cap(b)]
-	n, err := m.MarshalToSizedBuffer(b)
-	if err != nil {
-		return nil, err
-	}
-	return b[:n], nil
-}
-func (m *PromotionVariable) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_PromotionVariable.Merge(m, src)
-}
-func (m *PromotionVariable) XXX_Size() int {
-	return m.Size()
-}
-func (m *PromotionVariable) XXX_DiscardUnknown() {
-	xxx_messageInfo_PromotionVariable.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_PromotionVariable proto.InternalMessageInfo
-
 func (m *RepoSubscription) Reset()      { *m = RepoSubscription{} }
 func (*RepoSubscription) ProtoMessage() {}
 func (*RepoSubscription) Descriptor() ([]byte, []int) {
@@ -1871,6 +1871,7 @@ func init() {
 	proto.RegisterType((*DiscoveredCommit)(nil), "github.com.akuity.kargo.api.v1alpha1.DiscoveredCommit")
 	proto.RegisterType((*DiscoveredImageReference)(nil), "github.com.akuity.kargo.api.v1alpha1.DiscoveredImageReference")
 	proto.RegisterMapType((map[string]string)(nil), "github.com.akuity.kargo.api.v1alpha1.DiscoveredImageReference.AnnotationsEntry")
+	proto.RegisterType((*ExpressionVariable)(nil), "github.com.akuity.kargo.api.v1alpha1.ExpressionVariable")
 	proto.RegisterType((*Freight)(nil), "github.com.akuity.kargo.api.v1alpha1.Freight")
 	proto.RegisterType((*FreightCollection)(nil), "github.com.akuity.kargo.api.v1alpha1.FreightCollection")
 	proto.RegisterMapType((map[string]FreightReference)(nil), "github.com.akuity.kargo.api.v1alpha1.FreightCollection.ItemsEntry")
@@ -1910,7 +1911,6 @@ func init() {
 	proto.RegisterType((*PromotionTaskSpec)(nil), "github.com.akuity.kargo.api.v1alpha1.PromotionTaskSpec")
 	proto.RegisterType((*PromotionTemplate)(nil), "github.com.akuity.kargo.api.v1alpha1.PromotionTemplate")
 	proto.RegisterType((*PromotionTemplateSpec)(nil), "github.com.akuity.kargo.api.v1alpha1.PromotionTemplateSpec")
-	proto.RegisterType((*PromotionVariable)(nil), "github.com.akuity.kargo.api.v1alpha1.PromotionVariable")
 	proto.RegisterType((*RepoSubscription)(nil), "github.com.akuity.kargo.api.v1alpha1.RepoSubscription")
 	proto.RegisterType((*Stage)(nil), "github.com.akuity.kargo.api.v1alpha1.Stage")
 	proto.RegisterType((*StageList)(nil), "github.com.akuity.kargo.api.v1alpha1.StageList")
@@ -1931,267 +1931,268 @@ func init() {
 }
 
 var fileDescriptor_e26b7f7bbc391025 = []byte{
-	// 4156 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5c, 0xdd, 0x6f, 0x23, 0x47,
-	0x72, 0xdf, 0x21, 0x45, 0x4a, 0x2c, 0x4a, 0xbb, 0x52, 0xaf, 0xd6, 0xd6, 0xc9, 0xf1, 0x6a, 0x33,
-	0x36, 0x0c, 0x3b, 0xb6, 0xa9, 0xac, 0xec, 0xb5, 0xd7, 0x6b, 0x63, 0x03, 0x91, 0xda, 0x0f, 0xed,
-	0xe9, 0xbc, 0x4a, 0x53, 0x5e, 0x9f, 0xd7, 0x36, 0x36, 0x2d, 0xb2, 0x45, 0xce, 0x89, 0x9c, 0xa1,
-	0xa7, 0x9b, 0x3a, 0x2b, 0x09, 0x92, 0xcb, 0x27, 0x0e, 0x09, 0x12, 0xdc, 0x83, 0x01, 0x5f, 0x80,
-	0x04, 0x08, 0x92, 0xc7, 0x03, 0xf2, 0x9a, 0x87, 0x3c, 0xf8, 0x21, 0x2f, 0x46, 0x62, 0x04, 0x46,
-	0x12, 0x20, 0x0e, 0x70, 0xd0, 0xc5, 0x3a, 0x20, 0xff, 0x41, 0x5e, 0x16, 0x08, 0x10, 0xf4, 0xc7,
-	0xcc, 0xf4, 0x0c, 0x87, 0x2b, 0x0e, 0x25, 0x2d, 0x9c, 0x7b, 0x93, 0xba, 0xba, 0x7f, 0xd5, 0x5d,
-	0xdd, 0x55, 0x5d, 0x55, 0x5d, 0x43, 0x78, 0xb5, 0xe5, 0xf0, 0x76, 0x7f, 0xbb, 0xd2, 0xf0, 0xba,
-	0xcb, 0x64, 0xb7, 0xef, 0xf0, 0xfd, 0xe5, 0x5d, 0xe2, 0xb7, 0xbc, 0x65, 0xd2, 0x73, 0x96, 0xf7,
-	0x2e, 0x93, 0x4e, 0xaf, 0x4d, 0x2e, 0x2f, 0xb7, 0xa8, 0x4b, 0x7d, 0xc2, 0x69, 0xb3, 0xd2, 0xf3,
-	0x3d, 0xee, 0xa1, 0x67, 0xa3, 0x51, 0x15, 0x35, 0xaa, 0x22, 0x47, 0x55, 0x48, 0xcf, 0xa9, 0x04,
-	0xa3, 0x16, 0x5f, 0x36, 0xb0, 0x5b, 0x5e, 0xcb, 0x5b, 0x96, 0x83, 0xb7, 0xfb, 0x3b, 0xf2, 0x3f,
-	0xf9, 0x8f, 0xfc, 0x4b, 0x81, 0x2e, 0xde, 0xde, 0xbd, 0xca, 0x2a, 0x8e, 0xe4, 0x4c, 0x3f, 0xe6,
-	0xd4, 0x65, 0x8e, 0xe7, 0xb2, 0x97, 0x49, 0xcf, 0x61, 0xd4, 0xdf, 0xa3, 0xfe, 0x72, 0x6f, 0xb7,
-	0x25, 0x68, 0x2c, 0xde, 0x61, 0x79, 0x6f, 0x60, 0x7a, 0x8b, 0xaf, 0x46, 0x48, 0x5d, 0xd2, 0x68,
-	0x3b, 0x2e, 0xf5, 0xf7, 0xa3, 0xe1, 0x5d, 0xca, 0x49, 0xda, 0xa8, 0xe5, 0x61, 0xa3, 0xfc, 0xbe,
-	0xcb, 0x9d, 0x2e, 0x1d, 0x18, 0xf0, 0xda, 0x51, 0x03, 0x58, 0xa3, 0x4d, 0xbb, 0x24, 0x39, 0xce,
-	0xfe, 0x00, 0xce, 0xaf, 0xba, 0xa4, 0xb3, 0xcf, 0x1c, 0x86, 0xfb, 0xee, 0xaa, 0xdf, 0xea, 0x77,
-	0xa9, 0xcb, 0xd1, 0x25, 0x98, 0x70, 0x49, 0x97, 0x2e, 0x58, 0x97, 0xac, 0xe7, 0x4b, 0xd5, 0xe9,
-	0xcf, 0x0f, 0x96, 0xce, 0x1c, 0x1e, 0x2c, 0x4d, 0xbc, 0x4d, 0xba, 0x14, 0x4b, 0x0a, 0x7a, 0x06,
-	0x0a, 0x7b, 0xa4, 0xd3, 0xa7, 0x0b, 0x39, 0xd9, 0x65, 0x46, 0x77, 0x29, 0xdc, 0x13, 0x8d, 0x58,
-	0xd1, 0xec, 0x3f, 0xc8, 0xc7, 0xe0, 0xbf, 0x43, 0x39, 0x69, 0x12, 0x4e, 0x50, 0x17, 0x8a, 0x1d,
-	0xb2, 0x4d, 0x3b, 0x6c, 0xc1, 0xba, 0x94, 0x7f, 0xbe, 0xbc, 0x72, 0xa3, 0x32, 0xca, 0x26, 0x56,
-	0x52, 0xa0, 0x2a, 0x1b, 0x12, 0xe7, 0x86, 0xcb, 0xfd, 0xfd, 0xea, 0x59, 0x3d, 0x89, 0xa2, 0x6a,
-	0xc4, 0x9a, 0x09, 0xfa, 0x3d, 0x0b, 0xca, 0xc4, 0x75, 0x3d, 0x4e, 0xb8, 0xd8, 0xa6, 0x85, 0x9c,
-	0x64, 0x7a, 0x67, 0x7c, 0xa6, 0xab, 0x11, 0x98, 0xe2, 0x7c, 0x5e, 0x73, 0x2e, 0x1b, 0x14, 0x6c,
-	0xf2, 0x5c, 0x7c, 0x03, 0xca, 0xc6, 0x54, 0xd1, 0x2c, 0xe4, 0x77, 0xe9, 0xbe, 0x92, 0x2f, 0x16,
-	0x7f, 0xa2, 0xf9, 0x98, 0x40, 0xb5, 0x04, 0xaf, 0xe5, 0xae, 0x5a, 0x8b, 0xd7, 0x61, 0x36, 0xc9,
-	0x30, 0xcb, 0x78, 0xfb, 0xcf, 0x2d, 0x98, 0x37, 0x56, 0x81, 0xe9, 0x0e, 0xf5, 0xa9, 0xdb, 0xa0,
-	0x68, 0x19, 0x4a, 0x62, 0x2f, 0x59, 0x8f, 0x34, 0x82, 0xad, 0x9e, 0xd3, 0x0b, 0x29, 0xbd, 0x1d,
-	0x10, 0x70, 0xd4, 0x27, 0x3c, 0x16, 0xb9, 0x47, 0x1d, 0x8b, 0x5e, 0x9b, 0x30, 0xba, 0x90, 0x8f,
-	0x1f, 0x8b, 0x4d, 0xd1, 0x88, 0x15, 0xcd, 0x7e, 0x00, 0xdf, 0x0a, 0xe6, 0xb3, 0x45, 0xbb, 0xbd,
-	0x0e, 0xe1, 0x34, 0x9a, 0xd4, 0xd1, 0x47, 0xef, 0x12, 0x4c, 0xec, 0x3a, 0x6e, 0x33, 0x39, 0x8b,
-	0x6f, 0x3b, 0x6e, 0x13, 0x4b, 0x8a, 0xbd, 0x0b, 0x33, 0xab, 0xbd, 0x9e, 0xef, 0xed, 0xd1, 0x66,
-	0x9d, 0x93, 0x16, 0x45, 0xf7, 0x01, 0x88, 0x6e, 0x58, 0xe5, 0x12, 0xba, 0xbc, 0xf2, 0x2b, 0x15,
-	0xa5, 0x33, 0x15, 0x53, 0x67, 0x2a, 0xbd, 0xdd, 0x96, 0x68, 0x60, 0x15, 0xa1, 0x9a, 0x95, 0xbd,
-	0xcb, 0x95, 0x2d, 0xa7, 0x4b, 0xab, 0x67, 0x0f, 0x0f, 0x96, 0x60, 0x35, 0x44, 0xc0, 0x06, 0x9a,
-	0xfd, 0xfb, 0x16, 0x5c, 0x58, 0xf5, 0x5b, 0x5e, 0x6d, 0x6d, 0xb5, 0xd7, 0xbb, 0x4d, 0x49, 0x87,
-	0xb7, 0xeb, 0x9c, 0xf0, 0x3e, 0x43, 0xd7, 0xa1, 0xc8, 0xe4, 0x5f, 0x7a, 0x31, 0xcf, 0x05, 0xe7,
-	0x53, 0xd1, 0x1f, 0x1e, 0x2c, 0xcd, 0xa7, 0x0c, 0xa4, 0x58, 0x8f, 0x42, 0x2f, 0xc0, 0x64, 0x97,
-	0x32, 0x46, 0x5a, 0x81, 0xc4, 0xcf, 0x69, 0x80, 0xc9, 0xef, 0xa8, 0x66, 0x1c, 0xd0, 0xed, 0x7f,
-	0xca, 0xc1, 0xb9, 0x10, 0x4b, 0xb3, 0x3f, 0x85, 0xed, 0xed, 0xc3, 0x74, 0xdb, 0x58, 0xa1, 0xdc,
-	0xe5, 0xf2, 0xca, 0x9b, 0x23, 0x6a, 0x52, 0x9a, 0x90, 0xaa, 0xf3, 0x9a, 0xcd, 0xb4, 0xd9, 0x8a,
-	0x63, 0x6c, 0x50, 0x17, 0x80, 0xed, 0xbb, 0x0d, 0xcd, 0x74, 0x42, 0x32, 0x7d, 0x23, 0x23, 0xd3,
-	0x7a, 0x08, 0x50, 0x45, 0x9a, 0x25, 0x44, 0x6d, 0xd8, 0x60, 0x60, 0xff, 0x9d, 0x05, 0xe7, 0x53,
-	0xc6, 0xa1, 0xb7, 0x12, 0xfb, 0xf9, 0xec, 0xc0, 0x7e, 0xa2, 0x81, 0x61, 0xd1, 0x6e, 0xbe, 0x04,
-	0x53, 0x3e, 0xdd, 0x73, 0xc4, 0x4d, 0xa1, 0x25, 0x3c, 0xab, 0xc7, 0x4f, 0x61, 0xdd, 0x8e, 0xc3,
-	0x1e, 0xe8, 0x45, 0x28, 0x05, 0x7f, 0x0b, 0x31, 0xe7, 0x85, 0x32, 0x89, 0x8d, 0x0b, 0xba, 0x32,
-	0x1c, 0xd1, 0xed, 0xdf, 0x85, 0x42, 0xad, 0x4d, 0x7c, 0x2e, 0x4e, 0x8c, 0x4f, 0x7b, 0xde, 0x3b,
-	0x78, 0x43, 0x4f, 0x31, 0x3c, 0x31, 0x58, 0x35, 0xe3, 0x80, 0x3e, 0xc2, 0x66, 0xbf, 0x00, 0x93,
-	0x7b, 0xd4, 0x97, 0xf3, 0xcd, 0xc7, 0xc1, 0xee, 0xa9, 0x66, 0x1c, 0xd0, 0xed, 0x7f, 0xb3, 0x60,
-	0x5e, 0xce, 0x60, 0xcd, 0x61, 0x0d, 0x6f, 0x8f, 0xfa, 0xfb, 0x98, 0xb2, 0x7e, 0xe7, 0x84, 0x27,
-	0xb4, 0x06, 0xb3, 0x8c, 0x76, 0xf7, 0xa8, 0x5f, 0xf3, 0x5c, 0xc6, 0x7d, 0xe2, 0xb8, 0x5c, 0xcf,
-	0x6c, 0x41, 0xf7, 0x9e, 0xad, 0x27, 0xe8, 0x78, 0x60, 0x04, 0x7a, 0x1e, 0xa6, 0xf4, 0xb4, 0xc5,
-	0x51, 0x12, 0x82, 0x9d, 0x16, 0x7b, 0xa0, 0xd7, 0xc4, 0x70, 0x48, 0xb5, 0xff, 0xdb, 0x82, 0x39,
-	0xb9, 0xaa, 0x7a, 0x7f, 0x9b, 0x35, 0x7c, 0xa7, 0x27, 0x0c, 0xf0, 0x37, 0x71, 0x49, 0xd7, 0xe1,
-	0x6c, 0x33, 0x10, 0xfc, 0x86, 0xd3, 0x75, 0xb8, 0xd4, 0x91, 0x42, 0xf5, 0x09, 0x8d, 0x71, 0x76,
-	0x2d, 0x46, 0xc5, 0x89, 0xde, 0x6a, 0xfb, 0x3a, 0x7d, 0xc6, 0xa9, 0xbf, 0xe9, 0x7b, 0x5d, 0x4f,
-	0xac, 0x73, 0x8b, 0xb0, 0x5d, 0xf4, 0x1b, 0x30, 0xd5, 0xd5, 0x97, 0x9e, 0xb6, 0x9a, 0xbf, 0x3a,
-	0x9a, 0xd5, 0xbc, 0xbb, 0xfd, 0x3d, 0xda, 0xe0, 0xe2, 0xc2, 0x8c, 0xb4, 0x2d, 0x6a, 0xc3, 0x21,
-	0x2a, 0x7a, 0x0f, 0x26, 0x58, 0x8f, 0x36, 0xa4, 0x88, 0xca, 0x2b, 0xaf, 0x8f, 0xa6, 0xd4, 0xb1,
-	0x49, 0xd6, 0x7b, 0xb4, 0x11, 0xc9, 0x56, 0xfc, 0x87, 0x25, 0xa4, 0xfd, 0x9f, 0x16, 0x2c, 0xa4,
-	0xad, 0x6a, 0xc3, 0x61, 0x1c, 0x7d, 0x30, 0xb0, 0xb2, 0xca, 0x68, 0x2b, 0x13, 0xa3, 0xe5, 0xba,
-	0x42, 0xed, 0x0d, 0x5a, 0x8c, 0x55, 0x3d, 0x80, 0x82, 0xc3, 0x69, 0x37, 0x70, 0x35, 0xae, 0x8d,
-	0xb6, 0xac, 0xb4, 0xc9, 0x46, 0x57, 0xe8, 0xba, 0x00, 0xc4, 0x0a, 0xd7, 0x7e, 0x1f, 0xa6, 0x6b,
-	0x7d, 0xdf, 0xa7, 0x2e, 0x57, 0x17, 0xdc, 0xb7, 0xa1, 0xc0, 0x1c, 0x57, 0xdb, 0xf9, 0x6c, 0x77,
-	0x5b, 0x49, 0x80, 0xd7, 0xc5, 0x60, 0xac, 0x30, 0xec, 0xbf, 0xcc, 0xc3, 0xf9, 0xe0, 0xc4, 0xd0,
-	0xe6, 0xaa, 0xcf, 0x9d, 0x1d, 0xd2, 0xe0, 0x0c, 0x35, 0x61, 0xba, 0x19, 0x35, 0x73, 0x6d, 0x88,
-	0xb3, 0xf0, 0x0a, 0x8d, 0xbd, 0x01, 0xcf, 0x71, 0x0c, 0x15, 0xbd, 0x0b, 0xf9, 0x96, 0xc3, 0xb5,
-	0x67, 0x78, 0x75, 0x34, 0xc9, 0xdd, 0x72, 0x92, 0x96, 0xa7, 0x5a, 0xd6, 0xac, 0xf2, 0xb7, 0x1c,
-	0x8e, 0x05, 0x22, 0xda, 0x86, 0xa2, 0xd3, 0x25, 0x2d, 0x9a, 0x71, 0x57, 0xd6, 0xc5, 0x98, 0x24,
-	0x7a, 0xe8, 0x6a, 0x4a, 0x2a, 0xc3, 0x1a, 0x59, 0xf0, 0x68, 0x08, 0x8b, 0xa1, 0x6c, 0xf6, 0xe8,
-	0x3b, 0x9f, 0x62, 0x3b, 0x23, 0x1e, 0x92, 0xca, 0xb0, 0x46, 0xb6, 0xbf, 0xca, 0xc1, 0x6c, 0x24,
-	0xbf, 0x9a, 0xd7, 0xed, 0x3a, 0x1c, 0x2d, 0x42, 0xce, 0x69, 0x6a, 0x83, 0x04, 0x7a, 0x60, 0x6e,
-	0x7d, 0x0d, 0xe7, 0x9c, 0x26, 0x7a, 0x0e, 0x8a, 0xdb, 0x3e, 0x71, 0x1b, 0x6d, 0x6d, 0x88, 0x42,
-	0xe0, 0xaa, 0x6c, 0xc5, 0x9a, 0x8a, 0x9e, 0x86, 0x3c, 0x27, 0x2d, 0x6d, 0x7f, 0x42, 0xf9, 0x6d,
-	0x91, 0x16, 0x16, 0xed, 0xc2, 0xf0, 0xb1, 0xbe, 0xd4, 0x61, 0xb9, 0xf3, 0x86, 0xe1, 0xab, 0xab,
-	0x66, 0x1c, 0xd0, 0x05, 0x47, 0xd2, 0xe7, 0x6d, 0xcf, 0x5f, 0x28, 0xc4, 0x39, 0xae, 0xca, 0x56,
-	0xac, 0xa9, 0xc2, 0x45, 0x69, 0xc8, 0xf9, 0x73, 0xea, 0x2f, 0x14, 0xe3, 0x2e, 0x4a, 0x2d, 0x20,
-	0xe0, 0xa8, 0x0f, 0xfa, 0x10, 0xca, 0x0d, 0x9f, 0x12, 0xee, 0xf9, 0x6b, 0x84, 0xd3, 0x85, 0xc9,
-	0xcc, 0x27, 0xf0, 0x9c, 0xf0, 0xd2, 0x6b, 0x11, 0x04, 0x36, 0xf1, 0xec, 0xbf, 0xcf, 0xc3, 0x42,
-	0x24, 0x5a, 0xb9, 0xb7, 0x91, 0x67, 0xaa, 0xc5, 0x63, 0x0d, 0x11, 0xcf, 0x73, 0x50, 0x6c, 0x3a,
-	0x2d, 0xca, 0x78, 0x52, 0xca, 0x6b, 0xb2, 0x15, 0x6b, 0x2a, 0xfa, 0xe3, 0x44, 0x34, 0x52, 0x90,
-	0x07, 0xe5, 0xee, 0x68, 0x07, 0x65, 0xd8, 0xe4, 0xc6, 0x08, 0x49, 0xd0, 0x0a, 0x40, 0xcb, 0xe1,
-	0xfa, 0xd2, 0xd2, 0xbb, 0x1e, 0x1a, 0xeb, 0x5b, 0x21, 0x05, 0x1b, 0xbd, 0xd0, 0xbb, 0x50, 0x92,
-	0xf2, 0x1a, 0x53, 0xff, 0xa5, 0x0b, 0x53, 0x0b, 0x00, 0x70, 0x84, 0x75, 0xec, 0x20, 0xe7, 0xcb,
-	0x09, 0x98, 0xbc, 0xe9, 0x53, 0xa7, 0xd5, 0xe6, 0x8f, 0xe1, 0xd6, 0x7a, 0x06, 0x0a, 0xa4, 0xe3,
-	0x10, 0x26, 0x0f, 0xa0, 0x11, 0xe6, 0xac, 0x8a, 0x46, 0xac, 0x68, 0xe8, 0x7d, 0x28, 0x7a, 0xbe,
-	0xd3, 0x72, 0xdc, 0x85, 0x92, 0x9c, 0xc4, 0x2b, 0xa3, 0x6d, 0xb1, 0x5e, 0xc5, 0x5d, 0x39, 0x34,
-	0x3a, 0x45, 0xea, 0x7f, 0xac, 0x21, 0xd1, 0x7d, 0x98, 0x54, 0x5a, 0x11, 0x58, 0x9a, 0xe5, 0x91,
-	0x2d, 0xa5, 0x52, 0xac, 0x48, 0x7b, 0xd5, 0xff, 0x0c, 0x07, 0x80, 0xa8, 0x1e, 0x1a, 0xca, 0x09,
-	0x09, 0xfd, 0x62, 0x06, 0x43, 0x39, 0xd4, 0x32, 0xd6, 0x43, 0xcb, 0x58, 0xc8, 0x02, 0x2a, 0x6d,
-	0xdf, 0x30, 0x53, 0x28, 0x44, 0xac, 0x3d, 0xf2, 0xe2, 0x18, 0x22, 0xd6, 0xe1, 0xc0, 0xd9, 0xb8,
-	0x1b, 0x1f, 0x38, 0xec, 0xf6, 0x27, 0x79, 0x98, 0xd3, 0x3d, 0x6b, 0x5e, 0xa7, 0x43, 0x1b, 0xd2,
-	0xfd, 0x53, 0x86, 0x36, 0x9f, 0x6a, 0x68, 0x9d, 0xe0, 0xda, 0x57, 0x97, 0x57, 0x35, 0xd3, 0x6c,
-	0x22, 0x1e, 0x15, 0x79, 0xd5, 0x2b, 0x35, 0x0e, 0x77, 0x49, 0xf7, 0xd2, 0x0e, 0x00, 0xfa, 0x23,
-	0x0b, 0xce, 0xef, 0x51, 0xdf, 0xd9, 0x71, 0x1a, 0x52, 0x65, 0x6e, 0x3b, 0x8c, 0x7b, 0xfe, 0xbe,
-	0xbe, 0xda, 0x5e, 0x1b, 0x8d, 0xf3, 0x3d, 0x03, 0x60, 0xdd, 0xdd, 0xf1, 0xaa, 0x4f, 0x69, 0x6e,
-	0xe7, 0xef, 0x0d, 0x42, 0xe3, 0x34, 0x7e, 0x8b, 0x3d, 0x80, 0x68, 0xb6, 0x29, 0x1a, 0xbb, 0x61,
-	0x6a, 0xec, 0xc8, 0x13, 0x0b, 0x16, 0x1b, 0x98, 0x37, 0x53, 0xd3, 0x3f, 0xb3, 0xa0, 0xac, 0xe9,
-	0x8f, 0xc1, 0x93, 0xc3, 0x71, 0x4f, 0xee, 0xe5, 0x4c, 0xf3, 0x1f, 0xe2, 0xbc, 0xf9, 0x30, 0x13,
-	0x53, 0x72, 0x74, 0x45, 0x67, 0x34, 0xd4, 0xd5, 0xf2, 0xcb, 0x66, 0x46, 0xe3, 0xe1, 0xc1, 0xd2,
-	0x5c, 0xac, 0x73, 0x94, 0xe6, 0x38, 0x3a, 0xbc, 0xb8, 0x36, 0xf5, 0xe3, 0xbf, 0x5e, 0x3a, 0xf3,
-	0x83, 0x9f, 0x5e, 0x3a, 0x63, 0x7f, 0x9a, 0x87, 0xd9, 0xa4, 0x54, 0x47, 0xc8, 0xb5, 0x44, 0x36,
-	0x6c, 0xea, 0x54, 0x6d, 0x58, 0xee, 0xf4, 0x6c, 0x58, 0xfe, 0x34, 0x6c, 0xd8, 0xc4, 0x89, 0xd9,
-	0x30, 0xfb, 0x5f, 0x2c, 0x38, 0x1b, 0xee, 0xcc, 0x47, 0x7d, 0xe1, 0x22, 0x44, 0x52, 0xb7, 0x4e,
-	0x5e, 0xea, 0x0f, 0x60, 0x92, 0x79, 0x7d, 0xbf, 0x21, 0xfd, 0x60, 0x81, 0xfe, 0x6a, 0x36, 0xa3,
-	0xa9, 0xc6, 0x1a, 0xce, 0x9f, 0x6a, 0xc0, 0x01, 0xaa, 0xfd, 0x59, 0x2e, 0x5c, 0x90, 0xa6, 0x29,
-	0xdf, 0xc8, 0x17, 0x9e, 0xa3, 0x58, 0xd0, 0x94, 0xe9, 0x1b, 0x89, 0x56, 0xac, 0xa9, 0xc8, 0x96,
-	0xf6, 0x3c, 0x70, 0xd1, 0x4b, 0x55, 0xd0, 0x66, 0x59, 0x6e, 0x82, 0xa2, 0xa0, 0x1e, 0xcc, 0xfa,
-	0xf4, 0xa3, 0xbe, 0xe3, 0xd3, 0x66, 0xdd, 0x23, 0xbb, 0xc2, 0xaf, 0xd0, 0x79, 0xa8, 0x11, 0xf5,
-	0x7e, 0xad, 0xef, 0x4b, 0x13, 0x56, 0x9d, 0x17, 0xe1, 0x35, 0x4e, 0x60, 0xe1, 0x01, 0x74, 0xe4,
-	0xc1, 0x3c, 0xd9, 0x23, 0x4e, 0x87, 0x6c, 0x3b, 0x1d, 0x87, 0xef, 0xd7, 0xb9, 0x4f, 0x38, 0x6d,
-	0xed, 0x6b, 0x2f, 0xf8, 0x4d, 0xbd, 0x96, 0xf9, 0xd5, 0x94, 0x3e, 0x0f, 0x0f, 0x96, 0x9e, 0xd2,
-	0xb2, 0x48, 0x23, 0xe3, 0x54, 0x60, 0xfb, 0x67, 0x85, 0xd0, 0x42, 0xe8, 0xd4, 0xd3, 0x6f, 0x41,
-	0xb9, 0xa1, 0xe2, 0xbd, 0xce, 0xfe, 0xba, 0xab, 0xcf, 0xf4, 0xda, 0x18, 0xb7, 0x5d, 0xa5, 0x16,
-	0xc1, 0x24, 0x1c, 0x45, 0x83, 0x82, 0x4d, 0x6e, 0xe8, 0xfb, 0x00, 0xca, 0xf4, 0xd3, 0xe6, 0xba,
-	0xab, 0xef, 0xb6, 0xda, 0x38, 0xbc, 0xef, 0x85, 0x28, 0x8a, 0x75, 0xe8, 0x64, 0x45, 0x04, 0x6c,
-	0xb0, 0x12, 0xab, 0x0e, 0x12, 0xad, 0x37, 0x3d, 0x5f, 0x1b, 0x89, 0xb1, 0x56, 0xbd, 0x1a, 0xc1,
-	0x24, 0xdd, 0xe3, 0x88, 0x82, 0x4d, 0x6e, 0x8b, 0x3e, 0xcc, 0x26, 0x65, 0x95, 0x72, 0xbf, 0xdd,
-	0x8e, 0xdf, 0x6f, 0x2b, 0x23, 0x5a, 0x04, 0x23, 0x76, 0x37, 0x53, 0xfd, 0x3e, 0x9c, 0x4b, 0xc8,
-	0x28, 0x85, 0xe5, 0x7a, 0x9c, 0xe5, 0x2b, 0x59, 0xee, 0x7a, 0x9d, 0x10, 0x37, 0x79, 0x32, 0x98,
-	0x4d, 0x4a, 0xe7, 0xc4, 0x98, 0xc6, 0xb2, 0xf0, 0xe6, 0x25, 0xfe, 0x57, 0x39, 0x28, 0x85, 0x66,
-	0x3c, 0x4b, 0x4a, 0x4d, 0xb9, 0x5f, 0xb9, 0x23, 0xe2, 0xdc, 0xfc, 0x28, 0x71, 0xee, 0xc4, 0xf0,
-	0x38, 0x37, 0x48, 0xbb, 0x17, 0x1f, 0x9d, 0x76, 0x37, 0xe2, 0xdc, 0xc9, 0xd1, 0xe3, 0xdc, 0xa9,
-	0xa3, 0xe3, 0x5c, 0xfb, 0x6f, 0x2c, 0x40, 0x83, 0x49, 0x8d, 0x2c, 0x82, 0x22, 0xc9, 0xcb, 0xf5,
-	0xb5, 0xac, 0x11, 0xe6, 0x51, 0x77, 0xac, 0xfd, 0x59, 0x01, 0xce, 0xdd, 0x72, 0xc6, 0xce, 0x8e,
-	0x72, 0x78, 0x52, 0x21, 0xd5, 0xa9, 0x76, 0x7c, 0x43, 0xcb, 0xaa, 0xf6, 0xf7, 0x9a, 0x1e, 0xfa,
-	0x64, 0x2d, 0xbd, 0xdb, 0xc3, 0xe1, 0x24, 0x3c, 0x0c, 0x7a, 0xe4, 0x43, 0xf2, 0x26, 0xcc, 0x30,
-	0xee, 0x3b, 0x0d, 0xae, 0xf2, 0xaf, 0x6c, 0xa1, 0x2c, 0x6f, 0xae, 0x0b, 0xba, 0xfb, 0x4c, 0xdd,
-	0x24, 0xe2, 0x78, 0xdf, 0xd4, 0xb4, 0xee, 0x44, 0xe6, 0xb4, 0xee, 0x32, 0x94, 0x48, 0xa7, 0xe3,
-	0x7d, 0x7f, 0x8b, 0xb4, 0x98, 0x4e, 0xa4, 0x84, 0xa7, 0x66, 0x35, 0x20, 0xe0, 0xa8, 0x0f, 0xaa,
-	0x00, 0x38, 0x2d, 0xd7, 0xf3, 0xa9, 0x1c, 0x51, 0x94, 0x57, 0xa8, 0x7c, 0xba, 0x5a, 0x0f, 0x5b,
-	0xb1, 0xd1, 0x03, 0xd5, 0xe1, 0x82, 0xe3, 0x32, 0xda, 0xe8, 0xfb, 0xb4, 0xbe, 0xeb, 0xf4, 0xb6,
-	0x36, 0xea, 0xd2, 0x4a, 0xec, 0xcb, 0xd3, 0x3c, 0x55, 0x7d, 0x5a, 0x33, 0xbb, 0xb0, 0x9e, 0xd6,
-	0x09, 0xa7, 0x8f, 0x45, 0xaf, 0xc2, 0xb4, 0xe3, 0x36, 0x3a, 0xfd, 0x26, 0xdd, 0x24, 0xbc, 0xcd,
-	0x16, 0xa6, 0xe4, 0x34, 0x66, 0x0f, 0x0f, 0x96, 0xa6, 0xd7, 0x8d, 0x76, 0x1c, 0xeb, 0x25, 0x46,
-	0xd1, 0x8f, 0x8d, 0x51, 0xa5, 0x68, 0xd4, 0x8d, 0x8f, 0xcd, 0x51, 0x66, 0xaf, 0x94, 0xc4, 0x37,
-	0x64, 0x4a, 0x7c, 0xff, 0x24, 0x07, 0x45, 0xf5, 0xee, 0x84, 0xae, 0x24, 0x1e, 0x77, 0x9e, 0x1e,
-	0x78, 0xdc, 0x29, 0xa7, 0xbd, 0xd1, 0xd9, 0x50, 0x74, 0x18, 0xeb, 0xc7, 0x3d, 0x96, 0x75, 0xd9,
-	0x82, 0x35, 0x45, 0x26, 0x05, 0x3d, 0x77, 0xc7, 0x69, 0xe9, 0x8c, 0xc9, 0x75, 0xc3, 0x4f, 0x89,
-	0xaa, 0x07, 0x1e, 0x84, 0xe5, 0x05, 0x91, 0xcb, 0x12, 0xeb, 0x20, 0x7c, 0x97, 0x3b, 0xf5, 0xbb,
-	0x6f, 0x2b, 0x1e, 0x35, 0x89, 0x88, 0x35, 0xb2, 0xe0, 0xe1, 0xf5, 0x79, 0xaf, 0xcf, 0xe5, 0x41,
-	0x39, 0x21, 0x1e, 0x77, 0x25, 0x22, 0xd6, 0xc8, 0xf6, 0xa7, 0x16, 0x9c, 0x53, 0x32, 0xa8, 0xb5,
-	0x69, 0x63, 0xb7, 0xce, 0x69, 0x4f, 0x84, 0x10, 0x7d, 0x46, 0x59, 0x32, 0x84, 0x78, 0x87, 0x51,
-	0x86, 0x25, 0xc5, 0x58, 0x7d, 0xee, 0xb4, 0x56, 0x6f, 0x1f, 0xe6, 0xa0, 0x20, 0x7d, 0xf5, 0x2c,
-	0xf6, 0x27, 0x9e, 0xff, 0xca, 0x8d, 0x94, 0xff, 0x3a, 0x22, 0x45, 0x1a, 0xe5, 0x00, 0x27, 0x1e,
-	0x99, 0x03, 0x64, 0x69, 0x29, 0xc0, 0xb7, 0x32, 0x84, 0x28, 0xe3, 0x94, 0x20, 0x1c, 0x37, 0xc5,
-	0xf6, 0x73, 0x0b, 0xe6, 0xd3, 0x92, 0xe1, 0x59, 0x64, 0xfe, 0x12, 0x4c, 0xf5, 0x3a, 0x84, 0xef,
-	0x78, 0x7e, 0x37, 0xf9, 0x08, 0xba, 0xa9, 0xdb, 0x71, 0xd8, 0x03, 0xf9, 0x00, 0x7e, 0x10, 0xac,
-	0x06, 0x81, 0xdc, 0xf5, 0xe3, 0x25, 0x4a, 0xa3, 0x1d, 0x0e, 0x9b, 0x18, 0x36, 0xb8, 0xd8, 0x7f,
-	0x5a, 0x80, 0x39, 0x39, 0x64, 0xdc, 0x6b, 0x6d, 0x9c, 0x63, 0xd5, 0x83, 0x27, 0x64, 0x88, 0x39,
-	0x78, 0x13, 0xaa, 0x93, 0x76, 0x55, 0x8f, 0x7f, 0x62, 0x3d, 0xb5, 0xd7, 0xc3, 0xa1, 0x14, 0x3c,
-	0x04, 0x77, 0xf0, 0x7a, 0x83, 0x5f, 0xbc, 0xeb, 0xcd, 0x3c, 0x6c, 0x93, 0x47, 0x1e, 0xb6, 0xa1,
-	0x97, 0xe1, 0xd4, 0x31, 0x2e, 0xc3, 0xc1, 0x0b, 0xaa, 0x94, 0xe9, 0x82, 0xfa, 0x8b, 0x1c, 0x4c,
-	0x6e, 0xfa, 0x9e, 0x7c, 0x54, 0x39, 0xfd, 0xb4, 0xf6, 0xdd, 0xd8, 0x63, 0xec, 0xe5, 0x91, 0x1f,
-	0x63, 0x05, 0x94, 0x7c, 0x86, 0x9d, 0x8a, 0x3f, 0xc1, 0x1a, 0xf9, 0xd9, 0x7c, 0x96, 0xb0, 0x21,
-	0x80, 0x7c, 0x74, 0x7e, 0xf6, 0x33, 0x0b, 0xca, 0xba, 0xe7, 0x37, 0x36, 0x11, 0xa8, 0xe7, 0x37,
-	0x24, 0x11, 0xf8, 0x67, 0xd1, 0x0a, 0x84, 0xd0, 0xd0, 0xef, 0xc0, 0x5c, 0x2f, 0x78, 0xfc, 0xdd,
-	0xf4, 0x3a, 0x4e, 0xc3, 0xa1, 0x41, 0x2e, 0xf9, 0x4a, 0xc6, 0x97, 0x71, 0x39, 0x7c, 0xbf, 0xfa,
-	0x2d, 0xcd, 0x77, 0x6e, 0x33, 0x89, 0x8b, 0x07, 0x59, 0xd9, 0xff, 0x6e, 0xc1, 0x4c, 0x4c, 0xf6,
-	0xa8, 0x01, 0xd0, 0xf0, 0xdc, 0xa6, 0xc3, 0xc3, 0x3a, 0x94, 0xf2, 0xca, 0xf2, 0x68, 0x52, 0xad,
-	0x05, 0xe3, 0xa2, 0x43, 0x17, 0x36, 0x31, 0x6c, 0xc0, 0xa2, 0x57, 0x82, 0xa2, 0xb1, 0xb8, 0xe7,
-	0xa5, 0x8a, 0xc6, 0x1e, 0x1e, 0x2c, 0x4d, 0xeb, 0x39, 0x99, 0x45, 0x64, 0x59, 0x8a, 0xa3, 0xfe,
-	0x36, 0x07, 0xa5, 0x70, 0xfd, 0x8f, 0x41, 0x8d, 0xde, 0x89, 0xa9, 0xd1, 0x2b, 0x19, 0x77, 0x6e,
-	0x58, 0x3d, 0x03, 0xfa, 0x30, 0xa1, 0x4c, 0x59, 0x8f, 0xc4, 0x11, 0xea, 0xf4, 0x8f, 0x6a, 0xf3,
-	0x55, 0xdf, 0xc7, 0xa0, 0x50, 0x5b, 0x71, 0x85, 0x5a, 0xce, 0xb8, 0x9a, 0x21, 0x2a, 0xf5, 0x43,
-	0x0b, 0xce, 0x25, 0x94, 0x00, 0x3d, 0x03, 0x05, 0x99, 0x3b, 0xd4, 0xe7, 0x2b, 0x1c, 0xa8, 0xb3,
-	0x12, 0x92, 0x86, 0x36, 0x61, 0x9e, 0xf4, 0xb9, 0x17, 0x8e, 0xbd, 0xe1, 0x92, 0xed, 0x0e, 0x55,
-	0xa9, 0x86, 0xa9, 0xea, 0x2f, 0x85, 0x49, 0xbe, 0x94, 0x3e, 0x38, 0x75, 0xa4, 0xfd, 0x45, 0x0e,
-	0x50, 0xd8, 0x98, 0x25, 0xe9, 0xfe, 0x21, 0x4c, 0xee, 0xa8, 0xec, 0xd5, 0xf1, 0x5e, 0x4d, 0xaa,
-	0x65, 0xf3, 0xe1, 0x28, 0xc0, 0x44, 0xef, 0x9d, 0xcc, 0x39, 0x82, 0xc1, 0x33, 0x84, 0xee, 0x03,
-	0xec, 0x38, 0xae, 0xc3, 0xda, 0x63, 0xbe, 0x0f, 0xcb, 0xdb, 0xfc, 0x66, 0x88, 0x80, 0x0d, 0x34,
-	0xfb, 0x93, 0x9c, 0x71, 0x3e, 0xa5, 0xb9, 0x1c, 0x69, 0x5f, 0x5f, 0x88, 0x0b, 0xb3, 0x34, 0xf8,
-	0xa2, 0x66, 0x08, 0x66, 0x62, 0x8f, 0xf8, 0x41, 0x72, 0x3f, 0x6b, 0x2d, 0xd2, 0x3d, 0xe2, 0x3b,
-	0x62, 0xe3, 0xa3, 0x2d, 0xbd, 0x47, 0x7c, 0x86, 0x25, 0x24, 0xfa, 0xae, 0x98, 0x2a, 0xed, 0x05,
-	0x26, 0x34, 0xb3, 0x4d, 0xe0, 0xb4, 0x67, 0xae, 0x8f, 0xf6, 0x18, 0x56, 0x80, 0xf6, 0x27, 0x93,
-	0xc6, 0x81, 0xd7, 0x56, 0xfb, 0x0e, 0xa0, 0x0e, 0x61, 0xfc, 0x36, 0x71, 0x9b, 0xe2, 0x78, 0xd2,
-	0x1d, 0x9f, 0xb2, 0xb6, 0xf6, 0xd0, 0x16, 0x35, 0x0a, 0xda, 0x18, 0xe8, 0x81, 0x53, 0x46, 0xa1,
-	0x2b, 0x71, 0xe3, 0xbc, 0x94, 0x34, 0xce, 0x67, 0x23, 0x6d, 0x1b, 0xcf, 0x3c, 0x9b, 0xc7, 0xbd,
-	0x70, 0x0a, 0xc7, 0xfd, 0xb7, 0x61, 0x6e, 0x27, 0xf9, 0xc2, 0xaa, 0x0b, 0x47, 0x5e, 0x1f, 0xf3,
-	0x81, 0xb6, 0x7a, 0xe1, 0x30, 0x7a, 0x96, 0x8b, 0x9a, 0xf1, 0x20, 0x23, 0xe4, 0x05, 0x15, 0xb3,
-	0x32, 0x64, 0x56, 0xd9, 0x90, 0x91, 0x55, 0x2e, 0x11, 0x6c, 0x27, 0x6b, 0x65, 0x15, 0x24, 0x8e,
-	0x31, 0x48, 0xa8, 0x60, 0xf1, 0x24, 0x55, 0x10, 0x5d, 0x09, 0x5f, 0x21, 0xc4, 0x74, 0xa4, 0x2b,
-	0x9b, 0x1f, 0x78, 0x3f, 0x10, 0x24, 0x6c, 0xf6, 0x43, 0x3f, 0xb2, 0xe0, 0x82, 0x38, 0xac, 0x37,
-	0x3e, 0xa6, 0x8d, 0xbe, 0x90, 0x4a, 0x50, 0x48, 0xbf, 0x50, 0x96, 0xd2, 0x18, 0xb1, 0x7e, 0xb8,
-	0x9e, 0x06, 0x11, 0xf9, 0xe5, 0xa9, 0x64, 0x9c, 0xce, 0x18, 0x3d, 0x90, 0xa6, 0x83, 0x53, 0x19,
-	0xf6, 0x1c, 0x3f, 0x27, 0x51, 0xd2, 0x66, 0x87, 0x2b, 0xb3, 0xc3, 0xa9, 0xfd, 0xb3, 0xbc, 0x69,
-	0xad, 0x46, 0xcb, 0x94, 0xdc, 0x87, 0x09, 0x4e, 0xd8, 0xae, 0xd6, 0x82, 0xb7, 0xc6, 0xa8, 0x85,
-	0x8c, 0x74, 0x41, 0x7a, 0xe2, 0xb2, 0x49, 0x62, 0xa2, 0x45, 0xc8, 0x11, 0x96, 0xcc, 0x9b, 0xaf,
-	0x32, 0x9c, 0x23, 0x4c, 0xe6, 0xd4, 0x77, 0x74, 0x84, 0x14, 0xe5, 0xd4, 0x77, 0x70, 0xce, 0xd9,
-	0x41, 0xef, 0x41, 0xc1, 0xa7, 0xdc, 0xdf, 0xd7, 0xc6, 0xfc, 0xea, 0x18, 0x86, 0x0b, 0x8b, 0xf1,
-	0x4a, 0x44, 0xf2, 0x4f, 0xac, 0x10, 0x43, 0x73, 0x5b, 0x3c, 0x79, 0x73, 0x1b, 0xe5, 0x9c, 0xf2,
-	0xa7, 0x96, 0x73, 0xfa, 0x89, 0x65, 0x5c, 0xef, 0xe1, 0x3a, 0xd1, 0x3b, 0x30, 0xc9, 0x9d, 0x2e,
-	0xf5, 0xfa, 0x3c, 0x9b, 0xcf, 0x14, 0xbe, 0x4a, 0x4a, 0x2b, 0xb6, 0xa5, 0x20, 0x70, 0x80, 0x25,
-	0x02, 0x49, 0xea, 0xfb, 0x9e, 0xbf, 0xd5, 0x16, 0x56, 0xd9, 0xeb, 0x28, 0xc7, 0x64, 0x26, 0x0a,
-	0x24, 0x6f, 0xc4, 0xa8, 0x38, 0xd1, 0xdb, 0xfe, 0xc2, 0xf4, 0xee, 0xfe, 0xff, 0xd7, 0xf6, 0xfe,
-	0xb3, 0x05, 0x73, 0x8f, 0xbb, 0xa8, 0xf7, 0xbb, 0x71, 0x87, 0xf5, 0x95, 0x31, 0xd6, 0x33, 0xc4,
-	0x69, 0xfd, 0x00, 0x9e, 0x48, 0x57, 0xe3, 0x13, 0xf9, 0x1a, 0xe6, 0xf3, 0xa4, 0xac, 0xa4, 0xf3,
-	0x14, 0x68, 0x9f, 0x75, 0x8a, 0xce, 0x4e, 0xee, 0xa4, 0x9d, 0x1d, 0xdf, 0x5c, 0x89, 0xfe, 0x74,
-	0x08, 0x7d, 0xa8, 0x8f, 0x99, 0x95, 0xe5, 0x63, 0x94, 0x01, 0x98, 0xa1, 0x47, 0xed, 0x0b, 0x0b,
-	0x2e, 0xa4, 0xf6, 0x0e, 0x45, 0x98, 0x3b, 0x45, 0x11, 0x5a, 0x27, 0x2d, 0xc2, 0xfb, 0x86, 0x08,
-	0x83, 0x29, 0x9c, 0xd4, 0xf7, 0x7e, 0x3f, 0xce, 0xc1, 0x2c, 0xa6, 0x3d, 0x2f, 0x96, 0x3a, 0xdd,
-	0x0c, 0xea, 0xb9, 0x33, 0xc4, 0x1a, 0x89, 0x57, 0xc5, 0xea, 0x64, 0xac, 0x90, 0x5b, 0x28, 0x62,
-	0x37, 0xf0, 0x2c, 0x47, 0x16, 0xfc, 0x40, 0x52, 0x57, 0x5d, 0x49, 0x2a, 0x3d, 0xac, 0x00, 0x05,
-	0xb2, 0xac, 0xca, 0xd1, 0xd7, 0xc6, 0xeb, 0x19, 0xea, 0x7b, 0x06, 0x91, 0x65, 0x33, 0x56, 0x80,
-	0xf6, 0xa7, 0x39, 0x50, 0x71, 0xc9, 0x63, 0xb0, 0xbb, 0xbf, 0x1e, 0xb3, 0xbb, 0xcb, 0xa3, 0x7a,
-	0x57, 0x42, 0x3c, 0xc3, 0x72, 0x0f, 0xc9, 0x98, 0xf1, 0x72, 0x16, 0xd0, 0x47, 0xe7, 0x1d, 0xfe,
-	0xc1, 0x82, 0x92, 0xec, 0xf7, 0x18, 0x4c, 0xf8, 0x66, 0xdc, 0x84, 0xbf, 0x98, 0x61, 0x15, 0x43,
-	0x4c, 0xf7, 0x27, 0x79, 0x3d, 0xfb, 0x30, 0x22, 0x6d, 0x13, 0xbf, 0xa9, 0x63, 0xad, 0x48, 0x03,
-	0x45, 0x23, 0x56, 0x34, 0xf4, 0x9b, 0xaa, 0x80, 0x89, 0x32, 0x4e, 0x9b, 0x37, 0xc3, 0xc0, 0x27,
-	0x9f, 0xb9, 0x12, 0x4b, 0x57, 0x8b, 0x45, 0x39, 0x77, 0x9c, 0x40, 0xc5, 0x03, 0x7c, 0x44, 0x30,
-	0xd4, 0x4b, 0xda, 0x32, 0x1d, 0x24, 0xbc, 0x3e, 0xa6, 0xe1, 0x54, 0xc1, 0xd0, 0x40, 0x33, 0x1e,
-	0x64, 0x84, 0xda, 0x30, 0x6d, 0xd6, 0x90, 0xea, 0xb3, 0xb4, 0x92, 0xbd, 0x58, 0x55, 0x3d, 0x0c,
-	0x9b, 0x2d, 0x38, 0x86, 0x6c, 0x1f, 0x14, 0xa1, 0x6c, 0x1c, 0xbe, 0x44, 0x1e, 0x73, 0xe6, 0x74,
-	0xf2, 0x98, 0xe9, 0x61, 0x77, 0x79, 0xac, 0xb0, 0xfb, 0x72, 0x3c, 0xec, 0x7e, 0x2a, 0x19, 0x76,
-	0x83, 0x5c, 0x5d, 0x2c, 0xe4, 0x66, 0x70, 0x56, 0xc7, 0x9f, 0x41, 0x31, 0x70, 0xa6, 0x44, 0xc6,
-	0x60, 0x94, 0x8b, 0x84, 0x5f, 0x79, 0x33, 0x06, 0x89, 0x13, 0x2c, 0x84, 0x5f, 0xaa, 0x5b, 0xea,
-	0xfd, 0x6e, 0x97, 0xf8, 0xfb, 0x0b, 0xd3, 0x72, 0xc2, 0xa1, 0x5f, 0x7a, 0x33, 0x46, 0xc5, 0x89,
-	0xde, 0x68, 0x13, 0x8a, 0x2a, 0x7c, 0xd5, 0x05, 0xa6, 0x2f, 0x65, 0x89, 0x8c, 0x95, 0x5f, 0xae,
-	0xfe, 0xc6, 0x1a, 0xc7, 0xcc, 0x3c, 0x94, 0x8e, 0xc8, 0x3c, 0xdc, 0x01, 0xe4, 0x6d, 0xcb, 0x08,
-	0xa0, 0x79, 0x4b, 0x7d, 0x18, 0x2f, 0x4e, 0x65, 0x51, 0x86, 0xb5, 0xe1, 0x86, 0xdd, 0x1d, 0xe8,
-	0x81, 0x53, 0x46, 0x09, 0xad, 0xd6, 0x31, 0x6f, 0xa8, 0x0a, 0x3a, 0xcb, 0x90, 0x35, 0x66, 0x8a,
-	0x82, 0x38, 0x59, 0xa0, 0x58, 0x4b, 0xa0, 0xe2, 0x01, 0x3e, 0xe8, 0x23, 0x98, 0x11, 0x47, 0x28,
-	0x62, 0x0c, 0xc7, 0x64, 0x3c, 0x77, 0x78, 0xb0, 0x34, 0xb3, 0x61, 0x42, 0xe2, 0x38, 0x07, 0xfb,
-	0x4f, 0xf2, 0x90, 0x1e, 0x71, 0x47, 0xdf, 0x46, 0x58, 0x8f, 0xf8, 0x36, 0xe2, 0x5d, 0x28, 0x31,
-	0x4e, 0x7c, 0xf5, 0x1d, 0x49, 0x6e, 0xbc, 0xef, 0x48, 0xea, 0x01, 0x00, 0x8e, 0xb0, 0x12, 0xe9,
-	0x8f, 0xfc, 0x89, 0xa6, 0x3f, 0x56, 0x00, 0x64, 0x54, 0x55, 0xf3, 0xfa, 0xfa, 0xc1, 0x73, 0x26,
-	0xb2, 0x09, 0x37, 0x42, 0x0a, 0x36, 0x7a, 0xa1, 0xab, 0xe1, 0xc5, 0xa9, 0x5e, 0x38, 0x2f, 0x0d,
-	0x94, 0x95, 0x24, 0x13, 0x68, 0x29, 0x5f, 0x7f, 0x1f, 0x51, 0x86, 0x66, 0xff, 0x6f, 0x0e, 0x62,
-	0xc6, 0x10, 0xfd, 0xd0, 0x82, 0x39, 0x92, 0xf8, 0xc4, 0x3e, 0xf0, 0x25, 0x7f, 0x2d, 0xdb, 0xef,
-	0x1e, 0x0c, 0x7c, 0xa1, 0x1f, 0xbd, 0x29, 0x25, 0xbb, 0x30, 0x3c, 0xc8, 0x14, 0xfd, 0xa1, 0x05,
-	0xe7, 0xc9, 0xe0, 0x6f, 0x28, 0xe8, 0x4d, 0x7f, 0x63, 0xec, 0x1f, 0x61, 0xa8, 0x3e, 0x79, 0x78,
-	0xb0, 0x94, 0xf6, 0xeb, 0x12, 0x38, 0x8d, 0x1d, 0x7a, 0x1f, 0x26, 0x88, 0xdf, 0x0a, 0xf2, 0xaf,
-	0xd9, 0xd9, 0x06, 0x3f, 0x8d, 0x11, 0x79, 0x47, 0xab, 0x7e, 0x8b, 0x61, 0x09, 0x6a, 0xff, 0x34,
-	0x0f, 0xb3, 0xc9, 0x6f, 0x29, 0x74, 0xa5, 0xe2, 0x44, 0x6a, 0xa5, 0xa2, 0xd0, 0x91, 0x06, 0x0f,
-	0xcb, 0x06, 0x23, 0x1d, 0x11, 0x8d, 0x58, 0xd1, 0x42, 0x1d, 0x91, 0x15, 0xce, 0x85, 0x63, 0xe8,
-	0x88, 0x2c, 0x6b, 0x8e, 0xb0, 0xd0, 0xd5, 0xf8, 0xdd, 0x62, 0x27, 0xef, 0x96, 0x39, 0x73, 0x2d,
-	0xe3, 0x66, 0x75, 0xbb, 0x50, 0x36, 0xf6, 0x41, 0x6b, 0xe2, 0xb5, 0xcc, 0x72, 0x8f, 0x8e, 0xdd,
-	0x39, 0x55, 0xdc, 0x12, 0x51, 0x4c, 0xfc, 0x48, 0xef, 0xa5, 0xb4, 0x8e, 0x95, 0xf6, 0x94, 0xe2,
-	0x32, 0xd0, 0xec, 0xff, 0xb0, 0x60, 0x26, 0x56, 0x3e, 0x2b, 0xb8, 0x05, 0x65, 0xca, 0xe3, 0xff,
-	0x9e, 0xc4, 0xbd, 0x10, 0x01, 0x1b, 0x68, 0xe8, 0x7b, 0x50, 0xee, 0x78, 0x6e, 0x8b, 0x32, 0x5e,
-	0xf7, 0xc8, 0xae, 0xd6, 0x93, 0xac, 0x49, 0xa4, 0x85, 0xc3, 0x83, 0xa5, 0xf9, 0x0d, 0x05, 0x53,
-	0xf3, 0xba, 0xbd, 0x0e, 0xe5, 0xaa, 0xa0, 0x1d, 0x9b, 0xe0, 0xf2, 0x65, 0xf4, 0x5d, 0xe2, 0xd3,
-	0xb6, 0xd7, 0x67, 0xf4, 0x9b, 0xfa, 0x32, 0x1a, 0x4e, 0xf0, 0xa4, 0x5f, 0x46, 0x23, 0xe0, 0xa3,
-	0x5f, 0x46, 0xc3, 0xbe, 0xdf, 0xd8, 0x97, 0xd1, 0x70, 0x86, 0x43, 0x22, 0x95, 0xff, 0xc9, 0x19,
-	0xab, 0x88, 0x47, 0x2b, 0xb9, 0x47, 0x44, 0x2b, 0x1f, 0xc0, 0x94, 0xe3, 0x72, 0xea, 0xef, 0x91,
-	0x8e, 0xce, 0x01, 0x67, 0x3d, 0x8b, 0xe1, 0x52, 0xd7, 0x35, 0x0e, 0x0e, 0x11, 0x51, 0x07, 0x2e,
-	0x04, 0x6f, 0x26, 0x3e, 0x25, 0xd1, 0x9b, 0xad, 0xae, 0x7b, 0x7a, 0x2d, 0x48, 0xee, 0xdf, 0x4c,
-	0xeb, 0xf4, 0x70, 0x18, 0x01, 0xa7, 0x83, 0x22, 0x06, 0x33, 0xcc, 0x08, 0xd3, 0x83, 0x1b, 0x71,
-	0xc4, 0xf7, 0xa6, 0x64, 0x66, 0xc3, 0x28, 0x96, 0x32, 0x41, 0x71, 0x9c, 0x87, 0xfd, 0xaf, 0x79,
-	0x38, 0x97, 0x38, 0x69, 0x89, 0x70, 0xa4, 0xf4, 0x38, 0xc3, 0x91, 0xe2, 0x58, 0xe1, 0x48, 0xba,
-	0xa7, 0x3c, 0x31, 0x96, 0xa7, 0xfc, 0xa6, 0xf2, 0x56, 0xf5, 0xce, 0xad, 0xaf, 0xe9, 0x82, 0xf8,
-	0x50, 0x9a, 0x1b, 0x26, 0x11, 0xc7, 0xfb, 0x4a, 0x77, 0xa2, 0x39, 0xf8, 0xdb, 0x04, 0xda, 0xd5,
-	0x7e, 0x23, 0x6b, 0x71, 0x60, 0x08, 0xa0, 0xdc, 0x89, 0x14, 0x02, 0x4e, 0x63, 0x57, 0xbd, 0xf3,
-	0xf9, 0xd7, 0x17, 0xcf, 0x7c, 0xf9, 0xf5, 0xc5, 0x33, 0x5f, 0x7d, 0x7d, 0xf1, 0xcc, 0x0f, 0x0e,
-	0x2f, 0x5a, 0x9f, 0x1f, 0x5e, 0xb4, 0xbe, 0x3c, 0xbc, 0x68, 0x7d, 0x75, 0x78, 0xd1, 0xfa, 0xaf,
-	0xc3, 0x8b, 0xd6, 0x8f, 0x7e, 0x7e, 0xf1, 0xcc, 0xfd, 0x67, 0x47, 0xf9, 0x45, 0xb3, 0xff, 0x0b,
-	0x00, 0x00, 0xff, 0xff, 0xb3, 0x95, 0x0d, 0x4d, 0xf8, 0x4c, 0x00, 0x00,
+	// 4174 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5c, 0x4d, 0x6c, 0x1c, 0x47,
+	0x76, 0x56, 0xcf, 0x70, 0x86, 0x9c, 0x37, 0xa4, 0x44, 0x96, 0x28, 0x9b, 0x4b, 0xc7, 0xa2, 0xd2,
+	0x36, 0x0c, 0x3b, 0xb6, 0x87, 0x91, 0x64, 0xd9, 0xb2, 0x6c, 0x28, 0xe0, 0x0c, 0xf5, 0x43, 0x2d,
+	0xd7, 0x62, 0x6a, 0x68, 0x79, 0x2d, 0xdb, 0x50, 0x8a, 0x33, 0xc5, 0x99, 0x5e, 0xce, 0x74, 0x8f,
+	0xbb, 0x6a, 0xb8, 0x62, 0x12, 0x24, 0x9b, 0x5f, 0x2c, 0x12, 0x24, 0xd8, 0xc3, 0x06, 0xde, 0x00,
+	0x09, 0x10, 0x24, 0xc7, 0x05, 0x72, 0xcd, 0x21, 0x07, 0x1f, 0x72, 0x71, 0x92, 0x4d, 0x60, 0x24,
+	0x01, 0xe2, 0x00, 0x0b, 0x66, 0xcd, 0x05, 0x72, 0xce, 0x25, 0x17, 0x01, 0x01, 0x82, 0xfa, 0xe9,
+	0xee, 0xea, 0x9e, 0x1e, 0x71, 0x7a, 0x48, 0x0a, 0xce, 0xde, 0xc8, 0x7a, 0x55, 0xdf, 0xab, 0x7a,
+	0x55, 0xef, 0xd5, 0x7b, 0xaf, 0x5e, 0x0f, 0xbc, 0xd6, 0x72, 0x78, 0xbb, 0xbf, 0x55, 0x69, 0x78,
+	0xdd, 0x65, 0xb2, 0xd3, 0x77, 0xf8, 0xde, 0xf2, 0x0e, 0xf1, 0x5b, 0xde, 0x32, 0xe9, 0x39, 0xcb,
+	0xbb, 0x17, 0x49, 0xa7, 0xd7, 0x26, 0x17, 0x97, 0x5b, 0xd4, 0xa5, 0x3e, 0xe1, 0xb4, 0x59, 0xe9,
+	0xf9, 0x1e, 0xf7, 0xd0, 0xf3, 0xd1, 0xa8, 0x8a, 0x1a, 0x55, 0x91, 0xa3, 0x2a, 0xa4, 0xe7, 0x54,
+	0x82, 0x51, 0x8b, 0xaf, 0x1a, 0xd8, 0x2d, 0xaf, 0xe5, 0x2d, 0xcb, 0xc1, 0x5b, 0xfd, 0x6d, 0xf9,
+	0x9f, 0xfc, 0x47, 0xfe, 0xa5, 0x40, 0x17, 0x6f, 0xef, 0x5c, 0x65, 0x15, 0x47, 0x72, 0xa6, 0x0f,
+	0x39, 0x75, 0x99, 0xe3, 0xb9, 0xec, 0x55, 0xd2, 0x73, 0x18, 0xf5, 0x77, 0xa9, 0xbf, 0xdc, 0xdb,
+	0x69, 0x09, 0x1a, 0x8b, 0x77, 0x58, 0xde, 0x1d, 0x98, 0xde, 0xe2, 0x6b, 0x11, 0x52, 0x97, 0x34,
+	0xda, 0x8e, 0x4b, 0xfd, 0xbd, 0x68, 0x78, 0x97, 0x72, 0x92, 0x36, 0x6a, 0x79, 0xd8, 0x28, 0xbf,
+	0xef, 0x72, 0xa7, 0x4b, 0x07, 0x06, 0xbc, 0x7e, 0xd8, 0x00, 0xd6, 0x68, 0xd3, 0x2e, 0x49, 0x8e,
+	0xb3, 0x3f, 0x84, 0xb3, 0x2b, 0x2e, 0xe9, 0xec, 0x31, 0x87, 0xe1, 0xbe, 0xbb, 0xe2, 0xb7, 0xfa,
+	0x5d, 0xea, 0x72, 0x74, 0x01, 0x26, 0x5c, 0xd2, 0xa5, 0x0b, 0xd6, 0x05, 0xeb, 0xc5, 0x52, 0x75,
+	0xfa, 0xb3, 0xfd, 0xa5, 0x53, 0x07, 0xfb, 0x4b, 0x13, 0xef, 0x90, 0x2e, 0xc5, 0x92, 0x82, 0x9e,
+	0x83, 0xc2, 0x2e, 0xe9, 0xf4, 0xe9, 0x42, 0x4e, 0x76, 0x99, 0xd1, 0x5d, 0x0a, 0xf7, 0x44, 0x23,
+	0x56, 0x34, 0xfb, 0x77, 0xf2, 0x31, 0xf8, 0x6f, 0x50, 0x4e, 0x9a, 0x84, 0x13, 0xd4, 0x85, 0x62,
+	0x87, 0x6c, 0xd1, 0x0e, 0x5b, 0xb0, 0x2e, 0xe4, 0x5f, 0x2c, 0x5f, 0xba, 0x51, 0x19, 0x65, 0x13,
+	0x2b, 0x29, 0x50, 0x95, 0x75, 0x89, 0x73, 0xc3, 0xe5, 0xfe, 0x5e, 0xf5, 0xb4, 0x9e, 0x44, 0x51,
+	0x35, 0x62, 0xcd, 0x04, 0xfd, 0x96, 0x05, 0x65, 0xe2, 0xba, 0x1e, 0x27, 0x5c, 0x6c, 0xd3, 0x42,
+	0x4e, 0x32, 0xbd, 0x33, 0x3e, 0xd3, 0x95, 0x08, 0x4c, 0x71, 0x3e, 0xab, 0x39, 0x97, 0x0d, 0x0a,
+	0x36, 0x79, 0x2e, 0xbe, 0x09, 0x65, 0x63, 0xaa, 0x68, 0x16, 0xf2, 0x3b, 0x74, 0x4f, 0xc9, 0x17,
+	0x8b, 0x3f, 0xd1, 0x7c, 0x4c, 0xa0, 0x5a, 0x82, 0xd7, 0x72, 0x57, 0xad, 0xc5, 0xeb, 0x30, 0x9b,
+	0x64, 0x98, 0x65, 0xbc, 0xfd, 0xc7, 0x16, 0xcc, 0x1b, 0xab, 0xc0, 0x74, 0x9b, 0xfa, 0xd4, 0x6d,
+	0x50, 0xb4, 0x0c, 0x25, 0xb1, 0x97, 0xac, 0x47, 0x1a, 0xc1, 0x56, 0xcf, 0xe9, 0x85, 0x94, 0xde,
+	0x09, 0x08, 0x38, 0xea, 0x13, 0x1e, 0x8b, 0xdc, 0xe3, 0x8e, 0x45, 0xaf, 0x4d, 0x18, 0x5d, 0xc8,
+	0xc7, 0x8f, 0xc5, 0x86, 0x68, 0xc4, 0x8a, 0x66, 0x3f, 0x80, 0xaf, 0x05, 0xf3, 0xd9, 0xa4, 0xdd,
+	0x5e, 0x87, 0x70, 0x1a, 0x4d, 0xea, 0xf0, 0xa3, 0x77, 0x01, 0x26, 0x76, 0x1c, 0xb7, 0x99, 0x9c,
+	0xc5, 0xd7, 0x1d, 0xb7, 0x89, 0x25, 0xc5, 0xde, 0x81, 0x99, 0x95, 0x5e, 0xcf, 0xf7, 0x76, 0x69,
+	0xb3, 0xce, 0x49, 0x8b, 0xa2, 0xfb, 0x00, 0x44, 0x37, 0xac, 0x70, 0x09, 0x5d, 0xbe, 0xf4, 0x0b,
+	0x15, 0xa5, 0x33, 0x15, 0x53, 0x67, 0x2a, 0xbd, 0x9d, 0x96, 0x68, 0x60, 0x15, 0xa1, 0x9a, 0x95,
+	0xdd, 0x8b, 0x95, 0x4d, 0xa7, 0x4b, 0xab, 0xa7, 0x0f, 0xf6, 0x97, 0x60, 0x25, 0x44, 0xc0, 0x06,
+	0x9a, 0xfd, 0xdb, 0x16, 0x9c, 0x5b, 0xf1, 0x5b, 0x5e, 0x6d, 0x75, 0xa5, 0xd7, 0xbb, 0x4d, 0x49,
+	0x87, 0xb7, 0xeb, 0x9c, 0xf0, 0x3e, 0x43, 0xd7, 0xa1, 0xc8, 0xe4, 0x5f, 0x7a, 0x31, 0x2f, 0x04,
+	0xe7, 0x53, 0xd1, 0x1f, 0xed, 0x2f, 0xcd, 0xa7, 0x0c, 0xa4, 0x58, 0x8f, 0x42, 0x2f, 0xc1, 0x64,
+	0x97, 0x32, 0x46, 0x5a, 0x81, 0xc4, 0xcf, 0x68, 0x80, 0xc9, 0x6f, 0xa8, 0x66, 0x1c, 0xd0, 0xed,
+	0x7f, 0xc8, 0xc1, 0x99, 0x10, 0x4b, 0xb3, 0x3f, 0x81, 0xed, 0xed, 0xc3, 0x74, 0xdb, 0x58, 0xa1,
+	0xdc, 0xe5, 0xf2, 0xa5, 0xb7, 0x46, 0xd4, 0xa4, 0x34, 0x21, 0x55, 0xe7, 0x35, 0x9b, 0x69, 0xb3,
+	0x15, 0xc7, 0xd8, 0xa0, 0x2e, 0x00, 0xdb, 0x73, 0x1b, 0x9a, 0xe9, 0x84, 0x64, 0xfa, 0x66, 0x46,
+	0xa6, 0xf5, 0x10, 0xa0, 0x8a, 0x34, 0x4b, 0x88, 0xda, 0xb0, 0xc1, 0xc0, 0xfe, 0x6b, 0x0b, 0xce,
+	0xa6, 0x8c, 0x43, 0x6f, 0x27, 0xf6, 0xf3, 0xf9, 0x81, 0xfd, 0x44, 0x03, 0xc3, 0xa2, 0xdd, 0x7c,
+	0x05, 0xa6, 0x7c, 0xba, 0xeb, 0x88, 0x9b, 0x42, 0x4b, 0x78, 0x56, 0x8f, 0x9f, 0xc2, 0xba, 0x1d,
+	0x87, 0x3d, 0xd0, 0xcb, 0x50, 0x0a, 0xfe, 0x16, 0x62, 0xce, 0x0b, 0x65, 0x12, 0x1b, 0x17, 0x74,
+	0x65, 0x38, 0xa2, 0xdb, 0xbf, 0x09, 0x85, 0x5a, 0x9b, 0xf8, 0x5c, 0x9c, 0x18, 0x9f, 0xf6, 0xbc,
+	0x77, 0xf1, 0xba, 0x9e, 0x62, 0x78, 0x62, 0xb0, 0x6a, 0xc6, 0x01, 0x7d, 0x84, 0xcd, 0x7e, 0x09,
+	0x26, 0x77, 0xa9, 0x2f, 0xe7, 0x9b, 0x8f, 0x83, 0xdd, 0x53, 0xcd, 0x38, 0xa0, 0xdb, 0xff, 0x6a,
+	0xc1, 0xbc, 0x9c, 0xc1, 0xaa, 0xc3, 0x1a, 0xde, 0x2e, 0xf5, 0xf7, 0x30, 0x65, 0xfd, 0xce, 0x31,
+	0x4f, 0x68, 0x15, 0x66, 0x19, 0xed, 0xee, 0x52, 0xbf, 0xe6, 0xb9, 0x8c, 0xfb, 0xc4, 0x71, 0xb9,
+	0x9e, 0xd9, 0x82, 0xee, 0x3d, 0x5b, 0x4f, 0xd0, 0xf1, 0xc0, 0x08, 0xf4, 0x22, 0x4c, 0xe9, 0x69,
+	0x8b, 0xa3, 0x24, 0x04, 0x3b, 0x2d, 0xf6, 0x40, 0xaf, 0x89, 0xe1, 0x90, 0x6a, 0xff, 0x97, 0x05,
+	0x73, 0x72, 0x55, 0xf5, 0xfe, 0x16, 0x6b, 0xf8, 0x4e, 0x4f, 0x18, 0xe0, 0xaf, 0xe2, 0x92, 0xae,
+	0xc3, 0xe9, 0x66, 0x20, 0xf8, 0x75, 0xa7, 0xeb, 0x70, 0xa9, 0x23, 0x85, 0xea, 0x53, 0x1a, 0xe3,
+	0xf4, 0x6a, 0x8c, 0x8a, 0x13, 0xbd, 0xd5, 0xf6, 0x75, 0xfa, 0x8c, 0x53, 0x7f, 0xc3, 0xf7, 0xba,
+	0x9e, 0x58, 0xe7, 0x26, 0x61, 0x3b, 0xe8, 0x57, 0x60, 0xaa, 0xab, 0x2f, 0x3d, 0x6d, 0x35, 0x7f,
+	0x71, 0x34, 0xab, 0x79, 0x77, 0xeb, 0x5b, 0xb4, 0xc1, 0xc5, 0x85, 0x19, 0x69, 0x5b, 0xd4, 0x86,
+	0x43, 0x54, 0xf4, 0x3e, 0x4c, 0xb0, 0x1e, 0x6d, 0x48, 0x11, 0x95, 0x2f, 0xbd, 0x31, 0x9a, 0x52,
+	0xc7, 0x26, 0x59, 0xef, 0xd1, 0x46, 0x24, 0x5b, 0xf1, 0x1f, 0x96, 0x90, 0xf6, 0x7f, 0x58, 0xb0,
+	0x90, 0xb6, 0xaa, 0x75, 0x87, 0x71, 0xf4, 0xe1, 0xc0, 0xca, 0x2a, 0xa3, 0xad, 0x4c, 0x8c, 0x96,
+	0xeb, 0x0a, 0xb5, 0x37, 0x68, 0x31, 0x56, 0xf5, 0x00, 0x0a, 0x0e, 0xa7, 0xdd, 0xc0, 0xd5, 0xb8,
+	0x36, 0xda, 0xb2, 0xd2, 0x26, 0x1b, 0x5d, 0xa1, 0x6b, 0x02, 0x10, 0x2b, 0x5c, 0xfb, 0x03, 0x98,
+	0xae, 0xf5, 0x7d, 0x9f, 0xba, 0x5c, 0x5d, 0x70, 0x5f, 0x87, 0x02, 0x73, 0x5c, 0x6d, 0xe7, 0xb3,
+	0xdd, 0x6d, 0x25, 0x01, 0x5e, 0x17, 0x83, 0xb1, 0xc2, 0xb0, 0xff, 0x2c, 0x0f, 0x67, 0x83, 0x13,
+	0x43, 0x9b, 0x2b, 0x3e, 0x77, 0xb6, 0x49, 0x83, 0x33, 0xd4, 0x84, 0xe9, 0x66, 0xd4, 0xcc, 0xb5,
+	0x21, 0xce, 0xc2, 0x2b, 0x34, 0xf6, 0x06, 0x3c, 0xc7, 0x31, 0x54, 0xf4, 0x1e, 0xe4, 0x5b, 0x0e,
+	0xd7, 0x9e, 0xe1, 0xd5, 0xd1, 0x24, 0x77, 0xcb, 0x49, 0x5a, 0x9e, 0x6a, 0x59, 0xb3, 0xca, 0xdf,
+	0x72, 0x38, 0x16, 0x88, 0x68, 0x0b, 0x8a, 0x4e, 0x97, 0xb4, 0x68, 0xc6, 0x5d, 0x59, 0x13, 0x63,
+	0x92, 0xe8, 0xa1, 0xab, 0x29, 0xa9, 0x0c, 0x6b, 0x64, 0xc1, 0xa3, 0x21, 0x2c, 0x86, 0xb2, 0xd9,
+	0xa3, 0xef, 0x7c, 0x8a, 0xed, 0x8c, 0x78, 0x48, 0x2a, 0xc3, 0x1a, 0xd9, 0xfe, 0x22, 0x07, 0xb3,
+	0x91, 0xfc, 0x6a, 0x5e, 0xb7, 0xeb, 0x70, 0xb4, 0x08, 0x39, 0xa7, 0xa9, 0x0d, 0x12, 0xe8, 0x81,
+	0xb9, 0xb5, 0x55, 0x9c, 0x73, 0x9a, 0xe8, 0x05, 0x28, 0x6e, 0xf9, 0xc4, 0x6d, 0xb4, 0xb5, 0x21,
+	0x0a, 0x81, 0xab, 0xb2, 0x15, 0x6b, 0x2a, 0x7a, 0x16, 0xf2, 0x9c, 0xb4, 0xb4, 0xfd, 0x09, 0xe5,
+	0xb7, 0x49, 0x5a, 0x58, 0xb4, 0x0b, 0xc3, 0xc7, 0xfa, 0x52, 0x87, 0xe5, 0xce, 0x1b, 0x86, 0xaf,
+	0xae, 0x9a, 0x71, 0x40, 0x17, 0x1c, 0x49, 0x9f, 0xb7, 0x3d, 0x7f, 0xa1, 0x10, 0xe7, 0xb8, 0x22,
+	0x5b, 0xb1, 0xa6, 0x0a, 0x17, 0xa5, 0x21, 0xe7, 0xcf, 0xa9, 0xbf, 0x50, 0x8c, 0xbb, 0x28, 0xb5,
+	0x80, 0x80, 0xa3, 0x3e, 0xe8, 0x23, 0x28, 0x37, 0x7c, 0x4a, 0xb8, 0xe7, 0xaf, 0x12, 0x4e, 0x17,
+	0x26, 0x33, 0x9f, 0xc0, 0x33, 0xc2, 0x4b, 0xaf, 0x45, 0x10, 0xd8, 0xc4, 0xb3, 0xff, 0x26, 0x0f,
+	0x0b, 0x91, 0x68, 0xe5, 0xde, 0x46, 0x9e, 0xa9, 0x16, 0x8f, 0x35, 0x44, 0x3c, 0x2f, 0x40, 0xb1,
+	0xe9, 0xb4, 0x28, 0xe3, 0x49, 0x29, 0xaf, 0xca, 0x56, 0xac, 0xa9, 0xe8, 0xf7, 0x13, 0xd1, 0x48,
+	0x41, 0x1e, 0x94, 0xbb, 0xa3, 0x1d, 0x94, 0x61, 0x93, 0x1b, 0x23, 0x24, 0x41, 0x97, 0x00, 0x5a,
+	0x0e, 0xd7, 0x97, 0x96, 0xde, 0xf5, 0xd0, 0x58, 0xdf, 0x0a, 0x29, 0xd8, 0xe8, 0x85, 0xde, 0x83,
+	0x92, 0x94, 0xd7, 0x98, 0xfa, 0x2f, 0x5d, 0x98, 0x5a, 0x00, 0x80, 0x23, 0xac, 0x23, 0x07, 0x39,
+	0x1f, 0x00, 0xba, 0xf1, 0xb0, 0xe7, 0x53, 0x26, 0xae, 0xee, 0x7b, 0xc4, 0x77, 0xc8, 0x56, 0x87,
+	0x1e, 0x57, 0x1c, 0xfb, 0xf9, 0x04, 0x4c, 0xde, 0xf4, 0xa9, 0xd3, 0x6a, 0xf3, 0x27, 0x70, 0x25,
+	0x3e, 0x07, 0x05, 0xd2, 0x71, 0x08, 0x93, 0xa7, 0xdb, 0x98, 0xd2, 0x8a, 0x68, 0xc4, 0x8a, 0x86,
+	0x3e, 0x80, 0xa2, 0xe7, 0x3b, 0x2d, 0xc7, 0x5d, 0x28, 0xc9, 0x49, 0x5c, 0x1e, 0xed, 0xfc, 0xe8,
+	0x55, 0xdc, 0x95, 0x43, 0xa3, 0x23, 0xaa, 0xfe, 0xc7, 0x1a, 0x12, 0xdd, 0x87, 0x49, 0xa5, 0x72,
+	0x81, 0x19, 0x5b, 0x1e, 0xd9, 0x0c, 0x2b, 0xad, 0x8d, 0x4c, 0x83, 0xfa, 0x9f, 0xe1, 0x00, 0x10,
+	0xd5, 0x43, 0x2b, 0x3c, 0x21, 0xa1, 0x5f, 0xce, 0x60, 0x85, 0x87, 0x9a, 0xdd, 0x7a, 0x68, 0x76,
+	0x0b, 0x59, 0x40, 0xa5, 0x61, 0x1d, 0x66, 0x67, 0x85, 0x88, 0xb5, 0xbb, 0x5f, 0x1c, 0x43, 0xc4,
+	0x3a, 0xd6, 0x38, 0x1d, 0x8f, 0x11, 0x82, 0x68, 0xc0, 0xfe, 0x7e, 0x1e, 0xe6, 0x74, 0xcf, 0x9a,
+	0xd7, 0xe9, 0xd0, 0x86, 0xf4, 0x2d, 0x95, 0x15, 0xcf, 0xa7, 0x5a, 0x71, 0x27, 0xf0, 0x29, 0xd4,
+	0xcd, 0x58, 0xcd, 0x34, 0x9b, 0x88, 0x47, 0x45, 0xfa, 0x11, 0xca, 0x46, 0x84, 0xbb, 0xa4, 0x7b,
+	0x69, 0xef, 0x02, 0xfd, 0x9e, 0x05, 0x67, 0x77, 0xa9, 0xef, 0x6c, 0x3b, 0x0d, 0xa9, 0x8f, 0xb7,
+	0x1d, 0xc6, 0x3d, 0x7f, 0x4f, 0xdf, 0x9b, 0xaf, 0x8f, 0xc6, 0xf9, 0x9e, 0x01, 0xb0, 0xe6, 0x6e,
+	0x7b, 0xd5, 0x67, 0x34, 0xb7, 0xb3, 0xf7, 0x06, 0xa1, 0x71, 0x1a, 0xbf, 0xc5, 0x1e, 0x40, 0x34,
+	0xdb, 0x14, 0x73, 0xb0, 0x6e, 0x2a, 0xef, 0xc8, 0x13, 0x0b, 0x16, 0x1b, 0xd8, 0x4e, 0xd3, 0x8c,
+	0x7c, 0x6a, 0x41, 0x59, 0xd3, 0x9f, 0x80, 0x9b, 0x88, 0xe3, 0x6e, 0xe2, 0xab, 0x99, 0xe6, 0x3f,
+	0xc4, 0x33, 0xf4, 0x61, 0x26, 0xa6, 0xe4, 0xe8, 0x8a, 0x4e, 0x97, 0x28, 0x1b, 0xf8, 0xf3, 0x66,
+	0xba, 0xe4, 0xd1, 0xfe, 0xd2, 0x5c, 0xac, 0x73, 0x94, 0x43, 0x39, 0x3c, 0x76, 0xb9, 0x36, 0xf5,
+	0x83, 0xbf, 0x58, 0x3a, 0xf5, 0x9d, 0x1f, 0x5f, 0x38, 0x65, 0x7f, 0x92, 0x87, 0xd9, 0xa4, 0x54,
+	0x47, 0xb0, 0xbd, 0x91, 0x0d, 0x9b, 0x3a, 0x51, 0x1b, 0x96, 0x3b, 0x39, 0x1b, 0x96, 0x3f, 0x09,
+	0x1b, 0x36, 0x71, 0x6c, 0x36, 0xcc, 0xfe, 0x67, 0x0b, 0x4e, 0x87, 0x3b, 0xf3, 0x71, 0x5f, 0xf8,
+	0x1f, 0x91, 0xd4, 0xad, 0xe3, 0x97, 0xfa, 0x03, 0x98, 0x64, 0x5e, 0xdf, 0x6f, 0x48, 0x27, 0x5b,
+	0xa0, 0xbf, 0x96, 0xcd, 0x68, 0xaa, 0xb1, 0x86, 0x67, 0xa9, 0x1a, 0x70, 0x80, 0x6a, 0x7f, 0x9a,
+	0x0b, 0x17, 0xa4, 0x69, 0xca, 0xf1, 0xf2, 0x85, 0x5b, 0x2a, 0x16, 0x34, 0x65, 0x3a, 0x5e, 0xa2,
+	0x15, 0x6b, 0x2a, 0xb2, 0xa5, 0x3d, 0x0f, 0xfc, 0xff, 0x52, 0x15, 0xb4, 0x59, 0x96, 0x9b, 0xa0,
+	0x28, 0xa8, 0x07, 0xb3, 0x3e, 0xfd, 0xb8, 0xef, 0xf8, 0xb4, 0x59, 0xf7, 0xc8, 0x8e, 0x70, 0x5a,
+	0x74, 0x92, 0x6b, 0x44, 0xbd, 0x5f, 0xed, 0xfb, 0xd2, 0x84, 0x55, 0xe7, 0x45, 0xec, 0x8e, 0x13,
+	0x58, 0x78, 0x00, 0x1d, 0x79, 0x30, 0x4f, 0x76, 0x89, 0xd3, 0x21, 0x5b, 0x4e, 0xc7, 0xe1, 0x7b,
+	0x75, 0xee, 0x13, 0x4e, 0x5b, 0x7b, 0xda, 0xc5, 0x7e, 0x4b, 0xaf, 0x65, 0x7e, 0x25, 0xa5, 0xcf,
+	0xa3, 0xfd, 0xa5, 0x67, 0xb4, 0x2c, 0xd2, 0xc8, 0x38, 0x15, 0xd8, 0xfe, 0xcf, 0x42, 0x68, 0x21,
+	0x74, 0x5e, 0xeb, 0xd7, 0xa0, 0xdc, 0x50, 0xc1, 0x64, 0x67, 0x6f, 0xcd, 0xd5, 0x67, 0x7a, 0x75,
+	0x8c, 0xdb, 0xae, 0x52, 0x8b, 0x60, 0x12, 0x5e, 0xa8, 0x41, 0xc1, 0x26, 0x37, 0xf4, 0x6d, 0x00,
+	0x65, 0xfa, 0x69, 0x73, 0xcd, 0xd5, 0x77, 0x5b, 0x6d, 0x1c, 0xde, 0xf7, 0x42, 0x14, 0xc5, 0x3a,
+	0x74, 0xb2, 0x22, 0x02, 0x36, 0x58, 0x89, 0x55, 0x07, 0x59, 0xdc, 0x9b, 0x9e, 0xaf, 0x8d, 0xc4,
+	0x58, 0xab, 0x5e, 0x89, 0x60, 0x92, 0xbe, 0x77, 0x44, 0xc1, 0x26, 0xb7, 0x45, 0x1f, 0x66, 0x93,
+	0xb2, 0x4a, 0xb9, 0xdf, 0x6e, 0xc7, 0xef, 0xb7, 0x4b, 0x23, 0x5a, 0x04, 0x23, 0x31, 0x60, 0xbe,
+	0x23, 0xf8, 0x70, 0x26, 0x21, 0xa3, 0x14, 0x96, 0x6b, 0x71, 0x96, 0x97, 0xb3, 0xdc, 0xf5, 0x3a,
+	0xdb, 0x6e, 0xf2, 0x64, 0x30, 0x9b, 0x94, 0xce, 0xb1, 0x31, 0x8d, 0xa5, 0xf8, 0xcd, 0x4b, 0xfc,
+	0xcf, 0x73, 0x50, 0x0a, 0xcd, 0x78, 0x96, 0x7c, 0x9d, 0x72, 0xbf, 0x72, 0x87, 0x04, 0xd1, 0xf9,
+	0x51, 0x82, 0xe8, 0x89, 0xe1, 0x41, 0x74, 0x90, 0xd3, 0x2f, 0x3e, 0x3e, 0xa7, 0x6f, 0x04, 0xd1,
+	0x93, 0xa3, 0x07, 0xd1, 0x53, 0x87, 0x07, 0xd1, 0xf6, 0x5f, 0x5a, 0x80, 0x06, 0x33, 0x26, 0x59,
+	0x04, 0x45, 0x92, 0x97, 0xeb, 0xeb, 0x59, 0xc3, 0xd7, 0xc3, 0xee, 0x58, 0xfb, 0xd3, 0x02, 0x9c,
+	0xb9, 0xe5, 0x8c, 0x9d, 0x7a, 0xe5, 0xf0, 0xb4, 0x42, 0xaa, 0x53, 0xed, 0xf8, 0x86, 0x96, 0x55,
+	0xed, 0xef, 0x35, 0x3d, 0xf4, 0xe9, 0x5a, 0x7a, 0xb7, 0x47, 0xc3, 0x49, 0x78, 0x18, 0xf4, 0xc8,
+	0x87, 0xe4, 0x2d, 0x98, 0x61, 0xdc, 0x77, 0x1a, 0x5c, 0x25, 0x77, 0xd9, 0x42, 0x59, 0xde, 0x5c,
+	0xe7, 0x74, 0xf7, 0x99, 0xba, 0x49, 0xc4, 0xf1, 0xbe, 0xa9, 0x39, 0xe3, 0x89, 0xcc, 0x39, 0xe3,
+	0x65, 0x28, 0x91, 0x4e, 0xc7, 0xfb, 0xf6, 0x26, 0x69, 0x31, 0x9d, 0xa5, 0x09, 0x4f, 0xcd, 0x4a,
+	0x40, 0xc0, 0x51, 0x1f, 0x54, 0x01, 0x70, 0x5a, 0xae, 0xe7, 0x53, 0x39, 0xa2, 0x28, 0xaf, 0x50,
+	0xf9, 0x2e, 0xb6, 0x16, 0xb6, 0x62, 0xa3, 0x07, 0xaa, 0xc3, 0x39, 0xc7, 0x65, 0xb4, 0xd1, 0xf7,
+	0x69, 0x7d, 0xc7, 0xe9, 0x6d, 0xae, 0xd7, 0xa5, 0x95, 0xd8, 0x93, 0xa7, 0x79, 0xaa, 0xfa, 0xac,
+	0x66, 0x76, 0x6e, 0x2d, 0xad, 0x13, 0x4e, 0x1f, 0x8b, 0x5e, 0x83, 0x69, 0xc7, 0x6d, 0x74, 0xfa,
+	0x4d, 0xba, 0x41, 0x78, 0x9b, 0x2d, 0x4c, 0xc9, 0x69, 0xcc, 0x1e, 0xec, 0x2f, 0x4d, 0xaf, 0x19,
+	0xed, 0x38, 0xd6, 0x4b, 0x8c, 0xa2, 0x0f, 0x8d, 0x51, 0xa5, 0x68, 0xd4, 0x8d, 0x87, 0xe6, 0x28,
+	0xb3, 0x57, 0x4a, 0x56, 0x1d, 0x32, 0x65, 0xd5, 0x7f, 0x98, 0x83, 0xa2, 0x7a, 0xd4, 0x42, 0x57,
+	0x12, 0x2f, 0x47, 0xcf, 0x0e, 0xbc, 0x1c, 0x95, 0xd3, 0x1e, 0x00, 0x6d, 0x28, 0x3a, 0x8c, 0xf5,
+	0xe3, 0x1e, 0xcb, 0x9a, 0x6c, 0xc1, 0x9a, 0x22, 0x33, 0x8e, 0x9e, 0xbb, 0xed, 0xb4, 0x74, 0x3a,
+	0xe6, 0xba, 0xe1, 0xa7, 0x44, 0xa5, 0x09, 0x0f, 0xc2, 0xda, 0x85, 0xc8, 0x65, 0x89, 0x75, 0x10,
+	0xbe, 0xcb, 0x9d, 0xfa, 0xdd, 0x77, 0x14, 0x8f, 0x9a, 0x44, 0xc4, 0x1a, 0x59, 0xf0, 0xf0, 0xfa,
+	0xbc, 0xd7, 0xe7, 0xf2, 0xa0, 0x1c, 0x13, 0x8f, 0xbb, 0x12, 0x11, 0x6b, 0x64, 0xfb, 0x13, 0x0b,
+	0xce, 0x28, 0x19, 0xd4, 0xda, 0xb4, 0xb1, 0x53, 0xe7, 0xb4, 0x27, 0x42, 0x88, 0x3e, 0xa3, 0x2c,
+	0x19, 0x42, 0xbc, 0xcb, 0x28, 0xc3, 0x92, 0x62, 0xac, 0x3e, 0x77, 0x52, 0xab, 0xb7, 0x0f, 0x72,
+	0x50, 0x90, 0xbe, 0x7a, 0x16, 0xfb, 0x13, 0x4f, 0xae, 0xe5, 0x46, 0x4a, 0xae, 0x1d, 0x92, 0x7f,
+	0x8d, 0x12, 0x8c, 0x13, 0x8f, 0x4d, 0x30, 0xb2, 0xb4, 0xfc, 0xe2, 0xdb, 0x19, 0x42, 0x94, 0x71,
+	0xea, 0x1b, 0x8e, 0x9a, 0xbf, 0xfb, 0xa9, 0x05, 0xf3, 0x69, 0x99, 0xf6, 0x2c, 0x32, 0x7f, 0x05,
+	0xa6, 0x7a, 0x1d, 0xc2, 0xb7, 0x3d, 0xbf, 0x9b, 0x7c, 0x61, 0xdd, 0xd0, 0xed, 0x38, 0xec, 0x81,
+	0x7c, 0x00, 0x3f, 0x08, 0x56, 0x83, 0x40, 0xee, 0xfa, 0xd1, 0xb2, 0xb0, 0xd1, 0x0e, 0x87, 0x4d,
+	0x0c, 0x1b, 0x5c, 0xec, 0x3f, 0x2c, 0xc0, 0x9c, 0x1c, 0x32, 0xee, 0xb5, 0x36, 0xce, 0xb1, 0xea,
+	0xc1, 0x53, 0x32, 0xc4, 0x1c, 0xbc, 0x09, 0xd5, 0x49, 0xbb, 0xaa, 0xc7, 0x3f, 0xb5, 0x96, 0xda,
+	0xeb, 0xd1, 0x50, 0x0a, 0x1e, 0x82, 0x3b, 0x78, 0xbd, 0xc1, 0xcf, 0xde, 0xf5, 0x66, 0x1e, 0xb6,
+	0xc9, 0x43, 0x0f, 0xdb, 0xd0, 0xcb, 0x70, 0xea, 0x08, 0x97, 0xe1, 0xe0, 0x05, 0x55, 0xca, 0x74,
+	0x41, 0xfd, 0x69, 0x0e, 0x26, 0x37, 0x7c, 0x4f, 0xbe, 0xd8, 0x9c, 0x7c, 0x5a, 0xfb, 0x6e, 0xec,
+	0xa5, 0xf7, 0xe2, 0xc8, 0x2f, 0xbd, 0x02, 0x4a, 0xbe, 0xf1, 0x4e, 0xc5, 0xdf, 0x77, 0x8d, 0xfc,
+	0x6c, 0x3e, 0x4b, 0xd8, 0x10, 0x40, 0x3e, 0x3e, 0x3f, 0xfb, 0xa9, 0x05, 0x65, 0xdd, 0xf3, 0x2b,
+	0x9b, 0x08, 0xd4, 0xf3, 0x1b, 0x92, 0x08, 0xfc, 0xa3, 0x68, 0x05, 0x42, 0x68, 0xe8, 0x37, 0x60,
+	0xae, 0x17, 0xbc, 0x2c, 0x6f, 0x78, 0x1d, 0xa7, 0xe1, 0xd0, 0x20, 0x97, 0x7c, 0x25, 0xe3, 0xb3,
+	0xbb, 0x1c, 0xbe, 0x57, 0xfd, 0x9a, 0xe6, 0x3b, 0xb7, 0x91, 0xc4, 0xc5, 0x83, 0xac, 0xec, 0x7f,
+	0xb3, 0x60, 0x26, 0x26, 0x7b, 0xd4, 0x00, 0x68, 0x78, 0x6e, 0xd3, 0xe1, 0x61, 0x91, 0x4b, 0xf9,
+	0xd2, 0xf2, 0x68, 0x52, 0xad, 0x05, 0xe3, 0xa2, 0x43, 0x17, 0x36, 0x31, 0x6c, 0xc0, 0xa2, 0xcb,
+	0x41, 0x45, 0x5a, 0xdc, 0xf3, 0x52, 0x15, 0x69, 0x8f, 0xf6, 0x97, 0xa6, 0xf5, 0x9c, 0xcc, 0x0a,
+	0xb5, 0x2c, 0x95, 0x57, 0x7f, 0x95, 0x83, 0x52, 0xb8, 0xfe, 0x27, 0xa0, 0x46, 0xef, 0xc6, 0xd4,
+	0xe8, 0x72, 0xc6, 0x9d, 0x1b, 0x56, 0x2c, 0x81, 0x3e, 0x4a, 0x28, 0x53, 0xd6, 0x23, 0x71, 0x88,
+	0x3a, 0xfd, 0x9d, 0xda, 0x7c, 0xd5, 0xf7, 0x09, 0x28, 0xd4, 0x66, 0x5c, 0xa1, 0x96, 0x33, 0xae,
+	0x66, 0x88, 0x4a, 0x7d, 0xd7, 0x82, 0x33, 0x09, 0x25, 0x40, 0xcf, 0x41, 0x41, 0xe6, 0x0e, 0xf5,
+	0xf9, 0x0a, 0x07, 0xea, 0xac, 0x84, 0xa4, 0xa1, 0x0d, 0x98, 0x27, 0x7d, 0xee, 0x85, 0x63, 0x6f,
+	0xb8, 0x64, 0xab, 0x43, 0x55, 0xaa, 0x61, 0xaa, 0xfa, 0x73, 0x61, 0x92, 0x2f, 0xa5, 0x0f, 0x4e,
+	0x1d, 0x69, 0xff, 0x28, 0x07, 0x28, 0x6c, 0xcc, 0x92, 0x74, 0xff, 0x08, 0x26, 0xb7, 0x55, 0xf6,
+	0xea, 0x68, 0xaf, 0x26, 0xd5, 0xb2, 0xf9, 0x70, 0x14, 0x60, 0xa2, 0xf7, 0x8f, 0xe7, 0x1c, 0xc1,
+	0xe0, 0x19, 0x42, 0xf7, 0x01, 0xb6, 0x1d, 0xd7, 0x61, 0xed, 0x31, 0x1f, 0x9f, 0xe5, 0x6d, 0x7e,
+	0x33, 0x44, 0xc0, 0x06, 0x9a, 0xfd, 0x27, 0x39, 0xe3, 0x7c, 0x4a, 0x73, 0x39, 0xd2, 0xbe, 0xbe,
+	0x14, 0x17, 0x66, 0x69, 0xf0, 0x45, 0x2d, 0x14, 0xcc, 0x7d, 0x98, 0xd8, 0x25, 0x7e, 0x90, 0xdc,
+	0x1f, 0xb1, 0xae, 0x65, 0xf0, 0x49, 0x3b, 0xda, 0xd3, 0x7b, 0xc4, 0x67, 0x58, 0x62, 0xa2, 0x6f,
+	0x8a, 0xb9, 0xd2, 0x5e, 0x60, 0x43, 0x33, 0x1b, 0x05, 0x4e, 0x7b, 0xe6, 0x02, 0x69, 0x8f, 0x61,
+	0x05, 0x68, 0x7f, 0x7f, 0xd2, 0x38, 0xf1, 0xda, 0x6c, 0xdf, 0x01, 0xd4, 0x21, 0x8c, 0xdf, 0x26,
+	0x6e, 0x53, 0x9c, 0x4f, 0xba, 0xed, 0x53, 0xd6, 0xd6, 0x2e, 0xda, 0xa2, 0x46, 0x41, 0xeb, 0x03,
+	0x3d, 0x70, 0xca, 0x28, 0x74, 0x25, 0x6e, 0x9d, 0x97, 0x92, 0xd6, 0xf9, 0x74, 0xa4, 0x6e, 0xe3,
+	0xd9, 0x67, 0xf3, 0xbc, 0x17, 0x4e, 0xe0, 0xbc, 0xff, 0x3a, 0xcc, 0x6d, 0x27, 0x9f, 0x58, 0x75,
+	0x59, 0xca, 0x1b, 0x63, 0xbe, 0xd0, 0x56, 0xcf, 0x1d, 0x44, 0xef, 0x72, 0x51, 0x33, 0x1e, 0x64,
+	0x84, 0xbc, 0xa0, 0x1e, 0x57, 0xc6, 0xcc, 0x2a, 0x1d, 0x32, 0xb2, 0xce, 0x25, 0xa2, 0xed, 0x64,
+	0x25, 0xae, 0x82, 0xc4, 0x31, 0x06, 0x09, 0x1d, 0x2c, 0x1e, 0xa7, 0x0e, 0xa2, 0x2b, 0xe1, 0x33,
+	0x84, 0x98, 0x8e, 0xf4, 0x65, 0xf3, 0x03, 0x0f, 0x08, 0x82, 0x84, 0xcd, 0x7e, 0xe8, 0x7b, 0x16,
+	0x9c, 0x13, 0x87, 0xf5, 0xc6, 0x43, 0xda, 0xe8, 0x0b, 0xa9, 0x04, 0x65, 0xfa, 0x0b, 0x65, 0x29,
+	0x8d, 0x11, 0xab, 0x93, 0xeb, 0x69, 0x10, 0x91, 0x63, 0x9e, 0x4a, 0xc6, 0xe9, 0x8c, 0xd1, 0x03,
+	0x69, 0x3b, 0x38, 0x95, 0x71, 0xcf, 0xd1, 0x93, 0x12, 0x25, 0x6d, 0x77, 0xb8, 0xb2, 0x3b, 0x9c,
+	0xda, 0x3f, 0xc9, 0x9b, 0xe6, 0x6a, 0xb4, 0x54, 0xc9, 0x7d, 0x98, 0xe0, 0x84, 0xed, 0x68, 0x2d,
+	0x78, 0x7b, 0x8c, 0x4a, 0xcb, 0x48, 0x17, 0xa4, 0x2b, 0x2e, 0x9b, 0x24, 0x26, 0x5a, 0x84, 0x1c,
+	0x61, 0xc9, 0xc4, 0xf9, 0x0a, 0xc3, 0x39, 0xc2, 0x64, 0x52, 0x7d, 0x5b, 0x87, 0x48, 0x51, 0x52,
+	0x7d, 0x1b, 0xe7, 0x9c, 0x6d, 0xf4, 0x3e, 0x14, 0x7c, 0xca, 0xfd, 0x3d, 0x6d, 0xcd, 0xaf, 0x8e,
+	0x61, 0xb8, 0xb0, 0x18, 0xaf, 0x44, 0x24, 0xff, 0xc4, 0x0a, 0x31, 0xb4, 0xb7, 0xc5, 0x13, 0xb0,
+	0xb7, 0x51, 0xd6, 0x29, 0x7f, 0x62, 0x59, 0xa7, 0x1f, 0x5a, 0xc6, 0x05, 0x1f, 0x2e, 0x14, 0xbd,
+	0x0b, 0x93, 0xdc, 0xe9, 0x52, 0xaf, 0xcf, 0xb3, 0x79, 0x4d, 0xe1, 0xbb, 0xa4, 0x34, 0x63, 0x9b,
+	0x0a, 0x02, 0x07, 0x58, 0x22, 0x94, 0xa4, 0xbe, 0xef, 0xf9, 0x9b, 0x6d, 0x61, 0x96, 0xbd, 0x8e,
+	0x72, 0x4d, 0x66, 0xa2, 0x50, 0xf2, 0x46, 0x8c, 0x8a, 0x13, 0xbd, 0xed, 0x1f, 0x99, 0xfe, 0xdd,
+	0xff, 0xff, 0xd2, 0xe1, 0x7f, 0xb4, 0x60, 0xee, 0x49, 0xd7, 0x0c, 0x7f, 0x33, 0xee, 0xb2, 0x5e,
+	0x1e, 0x63, 0x3d, 0x43, 0xdc, 0xd6, 0x0f, 0xe1, 0xa9, 0x74, 0x3d, 0x3e, 0x96, 0x8f, 0x6d, 0xfe,
+	0x3e, 0x29, 0x2b, 0xe9, 0x3e, 0x05, 0xea, 0x67, 0x9d, 0xa4, 0xbb, 0x93, 0x3b, 0x6e, 0x77, 0xc7,
+	0x37, 0x97, 0xa2, 0x3f, 0x4d, 0x42, 0x1f, 0xe9, 0x73, 0x66, 0x65, 0xf9, 0xd8, 0x65, 0x00, 0x66,
+	0xe8, 0x59, 0xfb, 0x27, 0x0b, 0xce, 0xa5, 0xf6, 0x0e, 0x65, 0x98, 0x3b, 0x49, 0x19, 0x5a, 0xc7,
+	0x2d, 0xc3, 0x1f, 0xe4, 0x60, 0x16, 0xd3, 0x9e, 0x17, 0x4b, 0x71, 0x6e, 0x04, 0x45, 0xdd, 0x19,
+	0x62, 0x82, 0xc4, 0xeb, 0x5f, 0x75, 0x32, 0x56, 0xcd, 0x2d, 0xd4, 0xa5, 0x1b, 0x38, 0x80, 0x23,
+	0xab, 0xff, 0x40, 0xf2, 0x55, 0xdd, 0x1c, 0x2a, 0x8d, 0xab, 0x00, 0x05, 0xb2, 0xac, 0x9e, 0xd1,
+	0xc6, 0xfd, 0x8d, 0x0c, 0x75, 0x38, 0x83, 0xc8, 0xb2, 0x19, 0x2b, 0x40, 0xfb, 0x93, 0x1c, 0xa8,
+	0xf8, 0xe1, 0x09, 0x58, 0xc7, 0x5f, 0x8e, 0x59, 0xc7, 0xe5, 0x51, 0x9d, 0x20, 0x21, 0x9e, 0x61,
+	0x39, 0x82, 0x64, 0x6c, 0x77, 0x31, 0x0b, 0xe8, 0xe3, 0xf3, 0x03, 0x7f, 0x6b, 0x41, 0x49, 0xf6,
+	0x7b, 0x02, 0x86, 0x76, 0x23, 0x6e, 0x68, 0x5f, 0xce, 0xb0, 0x8a, 0x21, 0x06, 0xf6, 0xbf, 0xf3,
+	0x7a, 0xf6, 0x61, 0xe4, 0xd8, 0x26, 0x7e, 0x53, 0x87, 0x44, 0x91, 0x96, 0x88, 0x46, 0xac, 0x68,
+	0xa1, 0x6e, 0x4f, 0x9e, 0x80, 0x6e, 0xff, 0xaa, 0x2a, 0x62, 0xa2, 0x8c, 0xd3, 0xe6, 0xcd, 0x30,
+	0xf6, 0xc9, 0x67, 0xae, 0xc6, 0xd2, 0x15, 0x63, 0x51, 0xde, 0x1d, 0x27, 0x50, 0xf1, 0x00, 0x1f,
+	0x11, 0x0f, 0xf5, 0x92, 0xc6, 0x4c, 0xc7, 0x09, 0x6f, 0x8c, 0x69, 0x39, 0x55, 0x3c, 0x34, 0xd0,
+	0x8c, 0x07, 0x19, 0xa1, 0x36, 0x4c, 0x9b, 0x75, 0xa4, 0xfa, 0x9c, 0x5e, 0xca, 0x5e, 0xb0, 0xaa,
+	0x1e, 0x87, 0xcd, 0x16, 0x1c, 0x43, 0xb6, 0xf7, 0x8b, 0x50, 0x36, 0x0e, 0x76, 0x22, 0x97, 0x39,
+	0x73, 0x32, 0xb9, 0xcc, 0xf4, 0xc8, 0xbb, 0x3c, 0x56, 0xe4, 0x7d, 0x31, 0x1e, 0x79, 0x3f, 0x93,
+	0x8c, 0xbc, 0x41, 0xae, 0x2e, 0x16, 0x75, 0x33, 0x38, 0xad, 0x43, 0xd0, 0xa0, 0x20, 0x58, 0x25,
+	0x33, 0xc6, 0x0e, 0x74, 0x91, 0xf0, 0x2c, 0x6f, 0xc6, 0x20, 0x71, 0x82, 0x85, 0xf0, 0x4c, 0x75,
+	0x4b, 0xbd, 0xdf, 0xed, 0x12, 0x7f, 0x6f, 0x61, 0x5a, 0x4e, 0x38, 0xf4, 0x4c, 0x6f, 0xc6, 0xa8,
+	0x38, 0xd1, 0x1b, 0x6d, 0x40, 0x51, 0x45, 0xb0, 0xba, 0xc8, 0xf4, 0x95, 0x2c, 0xc1, 0xb1, 0xf2,
+	0xcc, 0xd5, 0xdf, 0x58, 0xe3, 0x98, 0xc9, 0x87, 0xd2, 0x21, 0xc9, 0x87, 0x3b, 0x80, 0xbc, 0x2d,
+	0x19, 0x03, 0x34, 0x6f, 0xa9, 0x2f, 0xef, 0xc5, 0xa9, 0x2c, 0xca, 0xc8, 0x36, 0xdc, 0xb0, 0xbb,
+	0x03, 0x3d, 0x70, 0xca, 0x28, 0xa1, 0xd5, 0x3a, 0xec, 0x0d, 0x55, 0x41, 0x27, 0x1a, 0xb2, 0x86,
+	0x4d, 0x51, 0x1c, 0x27, 0x8b, 0x14, 0x6b, 0x09, 0x54, 0x3c, 0xc0, 0x07, 0x7d, 0x0c, 0x33, 0xe2,
+	0x08, 0x45, 0x8c, 0xe1, 0x88, 0x8c, 0xe7, 0x0e, 0xf6, 0x97, 0x66, 0xd6, 0x4d, 0x48, 0x1c, 0xe7,
+	0x60, 0xff, 0x41, 0x1e, 0xd2, 0x83, 0xee, 0xe8, 0xfb, 0x08, 0xeb, 0x31, 0xdf, 0x47, 0xbc, 0x07,
+	0x25, 0xc6, 0x89, 0xaf, 0x3e, 0x54, 0xc9, 0x8d, 0xf7, 0xa1, 0x4a, 0x3d, 0x00, 0xc0, 0x11, 0x56,
+	0x22, 0x03, 0x92, 0x3f, 0xd6, 0x0c, 0xc8, 0x25, 0x00, 0x19, 0x57, 0xd5, 0xbc, 0xbe, 0x7e, 0xf4,
+	0x9c, 0x89, 0x6c, 0xc2, 0x8d, 0x90, 0x82, 0x8d, 0x5e, 0xe8, 0x6a, 0x78, 0x29, 0xab, 0x57, 0xce,
+	0x0b, 0x03, 0xa5, 0x25, 0xc9, 0x1c, 0x5a, 0xca, 0xe7, 0xe5, 0x87, 0x94, 0xa2, 0xd9, 0xff, 0x9b,
+	0x83, 0x98, 0x31, 0x44, 0xdf, 0xb5, 0x60, 0x8e, 0x24, 0xbe, 0xe1, 0x0f, 0x7c, 0xc9, 0x5f, 0xca,
+	0xf6, 0xc3, 0x0a, 0x03, 0x3f, 0x01, 0x10, 0xbd, 0x2b, 0x25, 0xbb, 0x30, 0x3c, 0xc8, 0x14, 0xfd,
+	0xae, 0x05, 0x67, 0xc9, 0xe0, 0x8f, 0x34, 0xe8, 0x4d, 0x7f, 0x73, 0xec, 0x5f, 0x79, 0xa8, 0x3e,
+	0x7d, 0xb0, 0xbf, 0x94, 0xf6, 0xf3, 0x15, 0x38, 0x8d, 0x1d, 0xfa, 0x00, 0x26, 0x88, 0xdf, 0x0a,
+	0x52, 0xb0, 0xd9, 0xd9, 0x06, 0xbf, 0xbd, 0x11, 0xdd, 0xe8, 0x2b, 0x7e, 0x8b, 0x61, 0x09, 0x6a,
+	0xff, 0x38, 0x0f, 0xb3, 0xc9, 0xef, 0x29, 0x74, 0xb5, 0xe2, 0x44, 0x6a, 0xb5, 0xa2, 0xd0, 0x91,
+	0x06, 0x0f, 0x4b, 0x07, 0x23, 0x1d, 0x11, 0x8d, 0x58, 0xd1, 0x42, 0x1d, 0x91, 0x55, 0xce, 0x85,
+	0x23, 0xe8, 0x88, 0x2c, 0x6d, 0x8e, 0xb0, 0xd0, 0xd5, 0xf8, 0xdd, 0x62, 0x27, 0xef, 0x96, 0x39,
+	0x73, 0x2d, 0xe3, 0x26, 0x76, 0xbb, 0x50, 0x36, 0xf6, 0x41, 0x6b, 0xe2, 0xb5, 0xcc, 0x72, 0x8f,
+	0x8e, 0xdd, 0x19, 0x55, 0xe0, 0x12, 0x51, 0x4c, 0xfc, 0x48, 0xef, 0xa5, 0xb4, 0x8e, 0x94, 0xf9,
+	0x94, 0xe2, 0x32, 0xd0, 0xec, 0x7f, 0xb7, 0x60, 0x26, 0x56, 0x42, 0x2b, 0xb8, 0x05, 0xa5, 0xca,
+	0xe3, 0xff, 0x60, 0xc5, 0xbd, 0x10, 0x01, 0x1b, 0x68, 0xe8, 0x5b, 0x50, 0xee, 0x78, 0x6e, 0x8b,
+	0x32, 0x5e, 0xf7, 0xc8, 0x8e, 0xd6, 0x93, 0xac, 0x69, 0xa4, 0x85, 0x83, 0xfd, 0xa5, 0xf9, 0x75,
+	0x05, 0x53, 0xf3, 0xba, 0xbd, 0x0e, 0xe5, 0xaa, 0xa8, 0x1d, 0x9b, 0xe0, 0xf2, 0x75, 0xf4, 0x3d,
+	0xe2, 0xd3, 0xb6, 0xd7, 0x67, 0xf4, 0xab, 0xfa, 0x3a, 0x1a, 0x4e, 0xf0, 0xb8, 0x5f, 0x47, 0x23,
+	0xe0, 0xc3, 0x5f, 0x47, 0xc3, 0xbe, 0x5f, 0xd9, 0xd7, 0xd1, 0x70, 0x86, 0x43, 0xa2, 0xa0, 0xff,
+	0xc9, 0x19, 0xab, 0x88, 0x47, 0x42, 0xb9, 0xc7, 0x44, 0x42, 0x1f, 0xc2, 0x94, 0xe3, 0x72, 0xea,
+	0xef, 0x92, 0x8e, 0x4e, 0x03, 0x67, 0x3d, 0x8b, 0xe1, 0x52, 0xd7, 0x34, 0x0e, 0x0e, 0x11, 0x51,
+	0x07, 0xce, 0x05, 0xcf, 0x26, 0x3e, 0x25, 0xd1, 0xbb, 0xad, 0xae, 0x7d, 0x7a, 0x3d, 0xc8, 0xef,
+	0xdf, 0x4c, 0xeb, 0xf4, 0x68, 0x18, 0x01, 0xa7, 0x83, 0x22, 0x06, 0x33, 0xcc, 0x48, 0x01, 0x04,
+	0x37, 0xe2, 0x88, 0x4f, 0x4e, 0xc9, 0xac, 0x89, 0x51, 0x30, 0x65, 0x82, 0xe2, 0x38, 0x0f, 0xfb,
+	0x5f, 0xf2, 0x70, 0x26, 0x71, 0xd2, 0x12, 0xe1, 0x48, 0xe9, 0x49, 0x86, 0x23, 0xc5, 0xb1, 0xc2,
+	0x91, 0x74, 0x4f, 0x79, 0x62, 0x2c, 0x4f, 0xf9, 0x2d, 0xe5, 0xad, 0xea, 0x9d, 0x5b, 0x5b, 0xd5,
+	0x45, 0xf1, 0xa1, 0x34, 0xd7, 0x4d, 0x22, 0x8e, 0xf7, 0x95, 0xee, 0x44, 0x73, 0xf0, 0xc7, 0x0f,
+	0xb4, 0xab, 0xfd, 0x66, 0xd6, 0x02, 0xc1, 0x10, 0x40, 0xb9, 0x13, 0x29, 0x04, 0x9c, 0xc6, 0xae,
+	0x7a, 0xe7, 0xb3, 0x2f, 0xcf, 0x9f, 0xfa, 0xfc, 0xcb, 0xf3, 0xa7, 0xbe, 0xf8, 0xf2, 0xfc, 0xa9,
+	0xef, 0x1c, 0x9c, 0xb7, 0x3e, 0x3b, 0x38, 0x6f, 0x7d, 0x7e, 0x70, 0xde, 0xfa, 0xe2, 0xe0, 0xbc,
+	0xf5, 0x93, 0x83, 0xf3, 0xd6, 0xf7, 0x7e, 0x7a, 0xfe, 0xd4, 0xfd, 0xe7, 0x47, 0xf9, 0xc9, 0xb4,
+	0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x28, 0x9c, 0x2e, 0x6d, 0x59, 0x4d, 0x00, 0x00,
 }
 
 func (m *AnalysisRunArgument) Marshal() (dAtA []byte, err error) {
@@ -2997,6 +2998,39 @@ func (m *DiscoveredImageReference) MarshalToSizedBuffer(dAtA []byte) (int, error
 	return len(dAtA) - i, nil
 }
 
+func (m *ExpressionVariable) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ExpressionVariable) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ExpressionVariable) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	i -= len(m.Value)
+	copy(dAtA[i:], m.Value)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.Value)))
+	i--
+	dAtA[i] = 0x12
+	i -= len(m.Name)
+	copy(dAtA[i:], m.Name)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
 func (m *Freight) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -4976,39 +5010,6 @@ func (m *PromotionTemplateSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
-func (m *PromotionVariable) Marshal() (dAtA []byte, err error) {
-	size := m.Size()
-	dAtA = make([]byte, size)
-	n, err := m.MarshalToSizedBuffer(dAtA[:size])
-	if err != nil {
-		return nil, err
-	}
-	return dAtA[:n], nil
-}
-
-func (m *PromotionVariable) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
-
-func (m *PromotionVariable) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	i -= len(m.Value)
-	copy(dAtA[i:], m.Value)
-	i = encodeVarintGenerated(dAtA, i, uint64(len(m.Value)))
-	i--
-	dAtA[i] = 0x12
-	i -= len(m.Name)
-	copy(dAtA[i:], m.Name)
-	i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
-	i--
-	dAtA[i] = 0xa
-	return len(dAtA) - i, nil
-}
-
 func (m *RepoSubscription) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -5188,6 +5189,20 @@ func (m *StageSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if len(m.Vars) > 0 {
+		for iNdEx := len(m.Vars) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Vars[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintGenerated(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x3a
+		}
+	}
 	if m.PromotionTemplate != nil {
 		{
 			size, err := m.PromotionTemplate.MarshalToSizedBuffer(dAtA[:i])
@@ -6132,6 +6147,19 @@ func (m *DiscoveredImageReference) Size() (n int) {
 	return n
 }
 
+func (m *ExpressionVariable) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Name)
+	n += 1 + l + sovGenerated(uint64(l))
+	l = len(m.Value)
+	n += 1 + l + sovGenerated(uint64(l))
+	return n
+}
+
 func (m *Freight) Size() (n int) {
 	if m == nil {
 		return 0
@@ -6866,19 +6894,6 @@ func (m *PromotionTemplateSpec) Size() (n int) {
 	return n
 }
 
-func (m *PromotionVariable) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	l = len(m.Name)
-	n += 1 + l + sovGenerated(uint64(l))
-	l = len(m.Value)
-	n += 1 + l + sovGenerated(uint64(l))
-	return n
-}
-
 func (m *RepoSubscription) Size() (n int) {
 	if m == nil {
 		return 0
@@ -6954,6 +6969,12 @@ func (m *StageSpec) Size() (n int) {
 		l = m.PromotionTemplate.Size()
 		n += 1 + l + sovGenerated(uint64(l))
 	}
+	if len(m.Vars) > 0 {
+		for _, e := range m.Vars {
+			l = e.Size()
+			n += 1 + l + sovGenerated(uint64(l))
+		}
+	}
 	return n
 }
 
@@ -7431,6 +7452,17 @@ func (this *DiscoveredImageReference) String() string {
 	}, "")
 	return s
 }
+func (this *ExpressionVariable) String() string {
+	if this == nil {
+		return "nil"
+	}
+	s := strings.Join([]string{`&ExpressionVariable{`,
+		`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
+		`Value:` + fmt.Sprintf("%v", this.Value) + `,`,
+		`}`,
+	}, "")
+	return s
+}
 func (this *Freight) String() string {
 	if this == nil {
 		return "nil"
@@ -7857,9 +7889,9 @@ func (this *PromotionSpec) String() string {
 		repeatedStringForSteps += strings.Replace(strings.Replace(f.String(), "PromotionStep", "PromotionStep", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForSteps += "}"
-	repeatedStringForVars := "[]PromotionVariable{"
+	repeatedStringForVars := "[]ExpressionVariable{"
 	for _, f := range this.Vars {
-		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "PromotionVariable", "PromotionVariable", 1), `&`, ``, 1) + ","
+		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "ExpressionVariable", "ExpressionVariable", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForVars += "}"
 	s := strings.Join([]string{`&PromotionSpec{`,
@@ -7904,9 +7936,9 @@ func (this *PromotionStep) String() string {
 	if this == nil {
 		return "nil"
 	}
-	repeatedStringForVars := "[]PromotionVariable{"
+	repeatedStringForVars := "[]ExpressionVariable{"
 	for _, f := range this.Vars {
-		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "PromotionVariable", "PromotionVariable", 1), `&`, ``, 1) + ","
+		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "ExpressionVariable", "ExpressionVariable", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForVars += "}"
 	s := strings.Join([]string{`&PromotionStep{`,
@@ -7974,9 +8006,9 @@ func (this *PromotionTaskSpec) String() string {
 	if this == nil {
 		return "nil"
 	}
-	repeatedStringForVars := "[]PromotionVariable{"
+	repeatedStringForVars := "[]ExpressionVariable{"
 	for _, f := range this.Vars {
-		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "PromotionVariable", "PromotionVariable", 1), `&`, ``, 1) + ","
+		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "ExpressionVariable", "ExpressionVariable", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForVars += "}"
 	repeatedStringForSteps := "[]PromotionStep{"
@@ -8010,9 +8042,9 @@ func (this *PromotionTemplateSpec) String() string {
 		repeatedStringForSteps += strings.Replace(strings.Replace(f.String(), "PromotionStep", "PromotionStep", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForSteps += "}"
-	repeatedStringForVars := "[]PromotionVariable{"
+	repeatedStringForVars := "[]ExpressionVariable{"
 	for _, f := range this.Vars {
-		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "PromotionVariable", "PromotionVariable", 1), `&`, ``, 1) + ","
+		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "ExpressionVariable", "ExpressionVariable", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForVars += "}"
 	s := strings.Join([]string{`&PromotionTemplateSpec{`,
@@ -8022,17 +8054,6 @@ func (this *PromotionTemplateSpec) String() string {
 	}, "")
 	return s
 }
-func (this *PromotionVariable) String() string {
-	if this == nil {
-		return "nil"
-	}
-	s := strings.Join([]string{`&PromotionVariable{`,
-		`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
-		`Value:` + fmt.Sprintf("%v", this.Value) + `,`,
-		`}`,
-	}, "")
-	return s
-}
 func (this *RepoSubscription) String() string {
 	if this == nil {
 		return "nil"
@@ -8082,11 +8103,17 @@ func (this *StageSpec) String() string {
 		repeatedStringForRequestedFreight += strings.Replace(strings.Replace(f.String(), "FreightRequest", "FreightRequest", 1), `&`, ``, 1) + ","
 	}
 	repeatedStringForRequestedFreight += "}"
+	repeatedStringForVars := "[]ExpressionVariable{"
+	for _, f := range this.Vars {
+		repeatedStringForVars += strings.Replace(strings.Replace(f.String(), "ExpressionVariable", "ExpressionVariable", 1), `&`, ``, 1) + ","
+	}
+	repeatedStringForVars += "}"
 	s := strings.Join([]string{`&StageSpec{`,
 		`Verification:` + strings.Replace(this.Verification.String(), "Verification", "Verification", 1) + `,`,
 		`Shard:` + fmt.Sprintf("%v", this.Shard) + `,`,
 		`RequestedFreight:` + repeatedStringForRequestedFreight + `,`,
 		`PromotionTemplate:` + strings.Replace(this.PromotionTemplate.String(), "PromotionTemplate", "PromotionTemplate", 1) + `,`,
+		`Vars:` + repeatedStringForVars + `,`,
 		`}`,
 	}, "")
 	return s
@@ -11041,6 +11068,120 @@ func (m *DiscoveredImageReference) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ExpressionVariable) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ExpressionVariable: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ExpressionVariable: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *Freight) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -15800,7 +15941,7 @@ func (m *PromotionSpec) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Vars = append(m.Vars, PromotionVariable{})
+			m.Vars = append(m.Vars, ExpressionVariable{})
 			if err := m.Vars[len(m.Vars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
@@ -16433,7 +16574,7 @@ func (m *PromotionStep) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Vars = append(m.Vars, PromotionVariable{})
+			m.Vars = append(m.Vars, ExpressionVariable{})
 			if err := m.Vars[len(m.Vars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
@@ -17001,7 +17142,7 @@ func (m *PromotionTaskSpec) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Vars = append(m.Vars, PromotionVariable{})
+			m.Vars = append(m.Vars, ExpressionVariable{})
 			if err := m.Vars[len(m.Vars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
@@ -17236,7 +17377,7 @@ func (m *PromotionTemplateSpec) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Vars = append(m.Vars, PromotionVariable{})
+			m.Vars = append(m.Vars, ExpressionVariable{})
 			if err := m.Vars[len(m.Vars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
 				return err
 			}
@@ -17262,120 +17403,6 @@ func (m *PromotionTemplateSpec) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
-func (m *PromotionVariable) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
-		preIndex := iNdEx
-		var wire uint64
-		for shift := uint(0); ; shift += 7 {
-			if shift >= 64 {
-				return ErrIntOverflowGenerated
-			}
-			if iNdEx >= l {
-				return io.ErrUnexpectedEOF
-			}
-			b := dAtA[iNdEx]
-			iNdEx++
-			wire |= uint64(b&0x7F) << shift
-			if b < 0x80 {
-				break
-			}
-		}
-		fieldNum := int32(wire >> 3)
-		wireType := int(wire & 0x7)
-		if wireType == 4 {
-			return fmt.Errorf("proto: PromotionVariable: wiretype end group for non-group")
-		}
-		if fieldNum <= 0 {
-			return fmt.Errorf("proto: PromotionVariable: illegal tag %d (wire type %d)", fieldNum, wire)
-		}
-		switch fieldNum {
-		case 1:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
-			}
-			var stringLen uint64
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowGenerated
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthGenerated
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthGenerated
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.Name = string(dAtA[iNdEx:postIndex])
-			iNdEx = postIndex
-		case 2:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
-			}
-			var stringLen uint64
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowGenerated
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthGenerated
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthGenerated
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.Value = string(dAtA[iNdEx:postIndex])
-			iNdEx = postIndex
-		default:
-			iNdEx = preIndex
-			skippy, err := skipGenerated(dAtA[iNdEx:])
-			if err != nil {
-				return err
-			}
-			if (skippy < 0) || (iNdEx+skippy) < 0 {
-				return ErrInvalidLengthGenerated
-			}
-			if (iNdEx + skippy) > l {
-				return io.ErrUnexpectedEOF
-			}
-			iNdEx += skippy
-		}
-	}
-
-	if iNdEx > l {
-		return io.ErrUnexpectedEOF
-	}
-	return nil
-}
 func (m *RepoSubscription) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -17967,6 +17994,40 @@ func (m *StageSpec) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 7:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Vars", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Vars = append(m.Vars, ExpressionVariable{})
+			if err := m.Vars[len(m.Vars)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipGenerated(dAtA[iNdEx:])
diff --git a/api/v1alpha1/generated.proto b/api/v1alpha1/generated.proto
index 6ee061a7..b72d98f9 100644
--- a/api/v1alpha1/generated.proto
+++ b/api/v1alpha1/generated.proto
@@ -302,6 +302,25 @@ message DiscoveredImageReference {
   optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time createdAt = 4;
 }
 
+// ExpressionVariable describes a single variable that may be referenced by
+// expressions in the context of a ClusterPromotionTask, PromotionTask,
+// Promotion, AnalysisRun arguments, or other objects that support expressions.
+//
+// It is used to pass information to the expression evaluation engine, and to
+// allow for dynamic evaluation of expressions based on the variable values.
+message ExpressionVariable {
+  // Name is the name of the variable.
+  //
+  // +kubebuilder:validation:MinLength=1
+  // +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
+  optional string name = 1;
+
+  // Value is the value of the variable. It is allowed to utilize expressions
+  // in the value.
+  // See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
+  optional string value = 2;
+}
+
 // Freight represents a collection of versioned artifacts.
 message Freight {
   optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
@@ -921,7 +940,7 @@ message PromotionSpec {
 
   // Vars is a list of variables that can be referenced by expressions in
   // promotion steps.
-  repeated PromotionVariable vars = 4;
+  repeated ExpressionVariable vars = 4;
 
   // Steps specifies the directives to be executed as part of this Promotion.
   // The order in which the directives are executed is the order in which they
@@ -1007,7 +1026,7 @@ message PromotionStep {
   // Vars is a list of variables that can be referenced by expressions in
   // the step's Config. The values override the values specified in the
   // PromotionSpec.
-  repeated PromotionVariable vars = 6;
+  repeated ExpressionVariable vars = 6;
 
   // Config is opaque configuration for the PromotionStep that is understood
   // only by each PromotionStep's implementation. It is legal to utilize
@@ -1095,7 +1114,7 @@ message PromotionTaskSpec {
   // Vars specifies the variables available to the PromotionTask. The
   // values of these variables are the default values that can be
   // overridden by the step referencing the task.
-  repeated PromotionVariable vars = 1;
+  repeated ExpressionVariable vars = 1;
 
   // Steps specifies the directives to be executed as part of this
   // PromotionTask. The steps as defined here are inflated into a
@@ -1119,7 +1138,7 @@ message PromotionTemplate {
 message PromotionTemplateSpec {
   // Vars is a list of variables that can be referenced by expressions in
   // promotion steps.
-  repeated PromotionVariable vars = 2;
+  repeated ExpressionVariable vars = 2;
 
   // Steps specifies the directives to be executed as part of a Promotion.
   // The order in which the directives are executed is the order in which they
@@ -1130,21 +1149,6 @@ message PromotionTemplateSpec {
   repeated PromotionStep steps = 1;
 }
 
-// PromotionVariable describes a single variable that may be referenced by
-// expressions in promotion steps.
-message PromotionVariable {
-  // Name is the name of the variable.
-  //
-  // +kubebuilder:validation:MinLength=1
-  // +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
-  optional string name = 1;
-
-  // Value is the value of the variable. It is allowed to utilize expressions
-  // in the value.
-  // See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
-  optional string value = 2;
-}
-
 // RepoSubscription describes a subscription to ONE OF a Git repository, a
 // container image repository, or a Helm chart repository.
 message RepoSubscription {
@@ -1189,6 +1193,11 @@ message StageSpec {
   // is empty, the webhook will ensure that label is absent.
   optional string shard = 4;
 
+  // Vars is a list of variables that can be referenced anywhere in the
+  // StageSpec that supports expressions. For example, the PromotionTemplate
+  // and arguments of the Verification.
+  repeated ExpressionVariable vars = 7;
+
   // RequestedFreight expresses the Stage's need for certain pieces of Freight,
   // each having originated from a particular Warehouse. This list must be
   // non-empty. In the common case, a Stage will request Freight having
diff --git a/api/v1alpha1/promotion_task_types.go b/api/v1alpha1/promotion_task_types.go
index 5c0fe35e..20270d10 100644
--- a/api/v1alpha1/promotion_task_types.go
+++ b/api/v1alpha1/promotion_task_types.go
@@ -23,7 +23,7 @@ type PromotionTaskSpec struct {
 	// Vars specifies the variables available to the PromotionTask. The
 	// values of these variables are the default values that can be
 	// overridden by the step referencing the task.
-	Vars []PromotionVariable `json:"vars,omitempty" protobuf:"bytes,1,rep,name=vars"`
+	Vars []ExpressionVariable `json:"vars,omitempty" protobuf:"bytes,1,rep,name=vars"`
 	// Steps specifies the directives to be executed as part of this
 	// PromotionTask. The steps as defined here are inflated into a
 	// Promotion when it is built from a PromotionTemplate.
diff --git a/api/v1alpha1/promotion_types.go b/api/v1alpha1/promotion_types.go
index 8e0cac73..1f8ba97e 100644
--- a/api/v1alpha1/promotion_types.go
+++ b/api/v1alpha1/promotion_types.go
@@ -96,7 +96,7 @@ type PromotionSpec struct {
 	Freight string `json:"freight" protobuf:"bytes,2,opt,name=freight"`
 	// Vars is a list of variables that can be referenced by expressions in
 	// promotion steps.
-	Vars []PromotionVariable `json:"vars,omitempty" protobuf:"bytes,4,rep,name=vars"`
+	Vars []ExpressionVariable `json:"vars,omitempty" protobuf:"bytes,4,rep,name=vars"`
 	// Steps specifies the directives to be executed as part of this Promotion.
 	// The order in which the directives are executed is the order in which they
 	// are listed in this field.
@@ -107,20 +107,6 @@ type PromotionSpec struct {
 	Steps []PromotionStep `json:"steps" protobuf:"bytes,3,rep,name=steps"`
 }
 
-// PromotionVariable describes a single variable that may be referenced by
-// expressions in promotion steps.
-type PromotionVariable struct {
-	// Name is the name of the variable.
-	//
-	// +kubebuilder:validation:MinLength=1
-	// +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
-	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
-	// Value is the value of the variable. It is allowed to utilize expressions
-	// in the value.
-	// See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
-	Value string `json:"value,omitempty" protobuf:"bytes,2,opt,name=value"`
-}
-
 // PromotionTaskReference describes a reference to a PromotionTask.
 type PromotionTaskReference struct {
 	// Name is the name of the (Cluster)PromotionTask.
@@ -217,7 +203,7 @@ type PromotionStep struct {
 	// Vars is a list of variables that can be referenced by expressions in
 	// the step's Config. The values override the values specified in the
 	// PromotionSpec.
-	Vars []PromotionVariable `json:"vars,omitempty" protobuf:"bytes,6,rep,name=vars"`
+	Vars []ExpressionVariable `json:"vars,omitempty" protobuf:"bytes,6,rep,name=vars"`
 	// Config is opaque configuration for the PromotionStep that is understood
 	// only by each PromotionStep's implementation. It is legal to utilize
 	// expressions in defining values at any level of this block.
diff --git a/api/v1alpha1/stage_types.go b/api/v1alpha1/stage_types.go
index 0f7045b2..7fd9f9e5 100644
--- a/api/v1alpha1/stage_types.go
+++ b/api/v1alpha1/stage_types.go
@@ -220,6 +220,10 @@ type StageSpec struct {
 	// kargo.akuity.io/shard label with the value of this field. When this field
 	// is empty, the webhook will ensure that label is absent.
 	Shard string `json:"shard,omitempty" protobuf:"bytes,4,opt,name=shard"`
+	// Vars is a list of variables that can be referenced anywhere in the
+	// StageSpec that supports expressions. For example, the PromotionTemplate
+	// and arguments of the Verification.
+	Vars []ExpressionVariable `json:"vars,omitempty" protobuf:"bytes,7,rep,name=vars"`
 	// RequestedFreight expresses the Stage's need for certain pieces of Freight,
 	// each having originated from a particular Warehouse. This list must be
 	// non-empty. In the common case, a Stage will request Freight having
@@ -329,7 +333,7 @@ type PromotionTemplate struct {
 type PromotionTemplateSpec struct {
 	// Vars is a list of variables that can be referenced by expressions in
 	// promotion steps.
-	Vars []PromotionVariable `json:"vars,omitempty" protobuf:"bytes,2,rep,name=vars"`
+	Vars []ExpressionVariable `json:"vars,omitempty" protobuf:"bytes,2,rep,name=vars"`
 	// Steps specifies the directives to be executed as part of a Promotion.
 	// The order in which the directives are executed is the order in which they
 	// are listed in this field.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index f3cab7fc..10f4fc31 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -364,6 +364,21 @@ func (in *DiscoveredImageReference) DeepCopy() *DiscoveredImageReference {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ExpressionVariable) DeepCopyInto(out *ExpressionVariable) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpressionVariable.
+func (in *ExpressionVariable) DeepCopy() *ExpressionVariable {
+	if in == nil {
+		return nil
+	}
+	out := new(ExpressionVariable)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Freight) DeepCopyInto(out *Freight) {
 	*out = *in
@@ -1014,7 +1029,7 @@ func (in *PromotionSpec) DeepCopyInto(out *PromotionSpec) {
 	*out = *in
 	if in.Vars != nil {
 		in, out := &in.Vars, &out.Vars
-		*out = make([]PromotionVariable, len(*in))
+		*out = make([]ExpressionVariable, len(*in))
 		copy(*out, *in)
 	}
 	if in.Steps != nil {
@@ -1099,7 +1114,7 @@ func (in *PromotionStep) DeepCopyInto(out *PromotionStep) {
 	}
 	if in.Vars != nil {
 		in, out := &in.Vars, &out.Vars
-		*out = make([]PromotionVariable, len(*in))
+		*out = make([]ExpressionVariable, len(*in))
 		copy(*out, *in)
 	}
 	if in.Config != nil {
@@ -1217,7 +1232,7 @@ func (in *PromotionTaskSpec) DeepCopyInto(out *PromotionTaskSpec) {
 	*out = *in
 	if in.Vars != nil {
 		in, out := &in.Vars, &out.Vars
-		*out = make([]PromotionVariable, len(*in))
+		*out = make([]ExpressionVariable, len(*in))
 		copy(*out, *in)
 	}
 	if in.Steps != nil {
@@ -1260,7 +1275,7 @@ func (in *PromotionTemplateSpec) DeepCopyInto(out *PromotionTemplateSpec) {
 	*out = *in
 	if in.Vars != nil {
 		in, out := &in.Vars, &out.Vars
-		*out = make([]PromotionVariable, len(*in))
+		*out = make([]ExpressionVariable, len(*in))
 		copy(*out, *in)
 	}
 	if in.Steps != nil {
@@ -1282,21 +1297,6 @@ func (in *PromotionTemplateSpec) DeepCopy() *PromotionTemplateSpec {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *PromotionVariable) DeepCopyInto(out *PromotionVariable) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PromotionVariable.
-func (in *PromotionVariable) DeepCopy() *PromotionVariable {
-	if in == nil {
-		return nil
-	}
-	out := new(PromotionVariable)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RepoSubscription) DeepCopyInto(out *RepoSubscription) {
 	*out = *in
@@ -1389,6 +1389,11 @@ func (in *StageList) DeepCopyObject() runtime.Object {
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *StageSpec) DeepCopyInto(out *StageSpec) {
 	*out = *in
+	if in.Vars != nil {
+		in, out := &in.Vars, &out.Vars
+		*out = make([]ExpressionVariable, len(*in))
+		copy(*out, *in)
+	}
 	if in.RequestedFreight != nil {
 		in, out := &in.RequestedFreight, &out.RequestedFreight
 		*out = make([]FreightRequest, len(*in))
diff --git a/charts/kargo/README.md b/charts/kargo/README.md
index 3b3b8532..2bdd5502 100644
--- a/charts/kargo/README.md
+++ b/charts/kargo/README.md
@@ -67,79 +67,80 @@ the Kargo controller is running.
 
 ### API
 
-| Name                                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Value                    |
-| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
-| `api.enabled`                               | Whether the API server is enabled.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `true`                   |
-| `api.replicas`                              | The number of API server pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `1`                      |
-| `api.host`                                  | The domain name where Kargo's API server will be accessible. When applicable, this is used for generation of an Ingress resource, certificates, and the OpenID Connect issuer and callback URLs. Note: The value in this field MAY include a port number and MUST NOT specify the protocol (http vs https), which is automatically inferred from other configuration options.                                                                                                                                                                                                                                                                      | `localhost`              |
-| `api.logLevel`                              | The log level for the API server.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `INFO`                   |
-| `api.labels`                                | Labels to add to the api resources. Merges with `global.labels`, allowing you to override or add to the global labels.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `{}`                     |
-| `api.annotations`                           | Annotations to add to the api resources. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `{}`                     |
-| `api.podLabels`                             | Optional labels to add to pods. Merges with `global.podLabels`, allowing you to override or add to the global labels.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `{}`                     |
-| `api.podAnnotations`                        | Optional annotations to add to pods. Merges with `global.podAnnotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
-| `api.secretManagementEnabled`               | Specifies whether Secret management is enabled. This affects the API server's ability to manage repository credentials and other Project-level Secrets, such as those used by AnalysisRuns for verification purposes. If using GitOps to manage Kargo Projects declaratively, the API's Secret management capabilities are not needed and can be disabled to effectively reduce the API server's attackable surface.                                                                                                                                                                                                                               | `true`                   |
-| `api.resources`                             | Resources limits and requests for the api containers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `{}`                     |
-| `api.nodeSelector`                          | Node selector for api pods. Defaults to `global.nodeSelector`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `{}`                     |
-| `api.tolerations`                           | Tolerations for api pods. Defaults to `global.tolerations`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `[]`                     |
-| `api.affinity`                              | Specifies pod affinity for api pods. Defaults to `global.affinity`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `{}`                     |
-| `api.securityContext`                       | Security context for api pods. Defaults to `global.securityContext`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
-| `api.cabundle.configMapName`                | Specifies the name of an optional ConfigMap containing CA certs that is managed "out of band." Values in the ConfigMap named here should each contain a single PEM-encoded CA cert. If secretName is also defined, it will take precedence over this field.                                                                                                                                                                                                                                                                                                                                                                                        | `""`                     |
-| `api.cabundle.secretName`                   | Specifies the name of an optional Secret containing CA certs that is managed "out of band." Values in the Secret named here should each contain a single PEM-encoded CA cert. If defined, the value of this field takes precedence over any in configMapName.                                                                                                                                                                                                                                                                                                                                                                                      | `""`                     |
-| `api.env`                                   | Environment variables to add to API server pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `[]`                     |
-| `api.envFrom`                               | Environment variables to add to API server pods from ConfigMaps or Secrets.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `[]`                     |
-| `api.probes.enabled`                        | Whether liveness and readiness probes should be included in the API server deployment. It is sometimes advantageous to disable these during local development.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `true`                   |
-| `api.tls.enabled`                           | Whether to enable TLS directly on the API server. This is helpful if you do not intend to use an ingress controller or if you require TLS end-to-end. All other settings in this section EXCEPT `terminatedUpstream` will be ignored when this is set to `false`.                                                                                                                                                                                                                                                                                                                                                                                  | `true`                   |
-| `api.tls.selfSignedCert`                    | Whether to generate a self-signed certificate for use by the API server. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-cert` **must** be provided in the same namespace as Kargo.                                                                                                                                                                                                                                                                                                                                             | `true`                   |
-| `api.tls.terminatedUpstream`                | Whether TLS is terminated upstream, i.e. a load balancer, reverse-proxy, or an Ingress controller using a single wildcard cert is terminating it. Setting this to `true` forces all API server URLs to use HTTPS even if the Ingress (if applicable) or API server itself are listening for plain HTTP requests.                                                                                                                                                                                                                                                                                                                                   | `false`                  |
-| `api.permissiveCORSPolicyEnabled`           | Whether to enable a permissive CORS (Cross Origin Resource Sharing) policy. This is sometimes advantageous during local development, but otherwise, should generally be left disabled.                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `false`                  |
-| `api.ingress.enabled`                       | Whether to enable ingress by creating an Ingress resource. By default, this is disabled. Enabling ingress is advanced usage.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `false`                  |
-| `api.ingress.annotations`                   | Annotations specified by your ingress controller to customize the behavior of the Ingress resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `{}`                     |
-| `api.ingress.ingressClassName`              | If implemented by your ingress controller, specifies the ingress class. If your ingress controller does not support this, use the `kubernetes.io/ingress.class` annotation instead.                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `nil`                    |
-| `api.ingress.tls.enabled`                   | Whether to associate a certificate with the Ingress resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `true`                   |
-| `api.ingress.tls.selfSignedCert`            | Whether to generate a self-signed certificate for use with the API server's Ingress resource. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-ingress-cert` **must** be provided in the same namespace as Kargo. The value in this field has no effect if `api.ingress.tls.enabled` is `false`.                                                                                                                                                                                                                                 | `true`                   |
-| `api.ingress.pathType`                      | You may want to use `Prefix` for some controllers (like AWS LoadBalancer Ingress controller), which don't support `/` as wildcard path when pathType is set to `ImplementationSpecific`                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `ImplementationSpecific` |
-| `api.service.type`                          | If you're not going to use an ingress controller, you may want to change this value to `LoadBalancer` for production deployments. If running locally, you may want to change it to `NodePort` OR leave it as `ClusterIP` and use `kubectl port-forward` to map a port on the local network interface to the service.                                                                                                                                                                                                                                                                                                                               | `ClusterIP`              |
-| `api.service.nodePort`                      | Host port the `Service` will be mapped to when `type` is either `NodePort` or `LoadBalancer`. If not specified, Kubernetes chooses.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `undefined`              |
-| `api.service.annotations`                   | Annotations to add to the API server's service. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `{}`                     |
-| `api.secret.name`                           | Specifies the name of an existing Secret which contains the `ADMIN_ACCOUNT_PASSWORD_HASH` and `ADMIN_ACCOUNT_TOKEN_SIGNING_KEY` values. By setting this, the Secret will **not** be generated by Helm.                                                                                                                                                                                                                                                                                                                                                                                                                                             | `""`                     |
-| `api.adminAccount.enabled`                  | Whether to enable the admin account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `true`                   |
-| `api.adminAccount.passwordHash`             | Bcrypt password hash for the admin account. A value **must** be provided for this field unless `api.secret.name` is specified.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `""`                     |
-| `api.adminAccount.tokenSigningKey`          | Key used to sign ID tokens (JWTs) for the admin account. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut`. A value **must** be provided for this field, unless `api.secret.name` is specified.                                                                                                                                                                                                                                                                                                                                                                   | `""`                     |
-| `api.adminAccount.tokenTTL`                 | Specifies how long ID tokens for the admin account are valid. (i.e. The expiry will be the time of issue plus this duration.)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `24h`                    |
-| `api.oidc.enabled`                          | Whether to enable authentication using Open ID Connect.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `false`                  |
-| `api.oidc.issuerURL`                        | The issuer URL for the identity provider. If Dex is enabled, this value will be ignored and the issuer URL will be automatically configured. If Dex is not enabled, this should be set to the issuer URL provided to you by your identity provider.                                                                                                                                                                                                                                                                                                                                                                                                | `nil`                    |
-| `api.oidc.clientID`                         | The client ID for the OIDC client. If Dex is enabled, this value will be ignored and the client ID will be automatically configured. If Dex is not enabled, this should be set to the client ID provided to you by your identity provider.                                                                                                                                                                                                                                                                                                                                                                                                         | `nil`                    |
-| `api.oidc.cliClientID`                      | The client ID for the OIDC client used by CLI (optional). Needed by some OIDC providers (such as Dex) that require a separate Client ID for web app login vs. CLI login (`http://localhost`). If Dex is enabled, this value will be ignored and cli client ID will be automatically configured. If Dex is not enabled, and a different client app is configured for localhost CLI login, this should be the client ID configured in the IdP.                                                                                                                                                                                                       | `nil`                    |
-| `api.oidc.additionalScopes`                 | The additional scopes to send to the OIDC provider. This should be set to the scopes you wish to be provided to your identity provider from clients of Kargo, the scopes openid, profile and email are always requested and don't need to be added, this value is intended for any additional ones you require.                                                                                                                                                                                                                                                                                                                                    | `["groups"]`             |
-| `api.oidc.admins.claims`                    | Subjects having any of these claims will automatically be Kargo admins.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `{}`                     |
-| `api.oidc.viewers.claims`                   | Subjects having any of these claims will automatically receive read-only access to all Kargo resources.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `{}`                     |
-| `api.oidc.globalServiceAccounts.namespaces` | List of namespaces to look for shared service accounts.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `[]`                     |
-| `api.oidc.dex.enabled`                      | Whether to enable Dex as the identity provider. When set to true, the Kargo installation will include a Dex server and the Kargo API server will be configured to make the /dex endpoint a reverse proxy for the Dex server.                                                                                                                                                                                                                                                                                                                                                                                                                       | `false`                  |
-| `api.oidc.dex.image.repository`             | Image repository of Dex                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `ghcr.io/dexidp/dex`     |
-| `api.oidc.dex.image.tag`                    | Image tag for Dex.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `v2.37.0`                |
-| `api.oidc.dex.image.pullPolicy`             | Image pull policy for Dex.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `IfNotPresent`           |
-| `api.oidc.dex.image.pullSecrets`            | List of imagePullSecrets.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | `[]`                     |
-| `api.oidc.dex.env`                          | Environment variables to add to Dex server pods. This is convenient for cases where api.oidc.dex.connectors needs to reference environment variables from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                         | `[]`                     |
-| `api.oidc.dex.envFrom`                      | Environment variables to add to Dex server pods from ConfigMaps or Secrets. This is especially convenient for cases where api.oidc.dex.connectors needs to reference environment variables from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                   | `[]`                     |
-| `api.oidc.dex.volumes`                      | Add additional volumes to Dex pods. This is convenient for cases where api.oidc.dex.connectors needs to reference mounted data from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                                               | `[]`                     |
-| `api.oidc.dex.volumeMounts`                 | Add additional volume mounts to Dex pods. This is convenient for cases where api.oidc.dex.connectors needs to reference mounted data from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                                         | `nil`                    |
-| `api.oidc.dex.probes.enabled`               | Whether liveness and readiness probes should be included in the Dex server deployment. It is sometimes advantageous to disable these during local development.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `true`                   |
-| `api.oidc.dex.tls.selfSignedCert`           | Whether to generate a self-signed certificate for use with Dex. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-dex-server-cert` **must** be provided in the same namespace as Kargo. There is no provision for running Dex without TLS.                                                                                                                                                                                                                                                                                            | `true`                   |
-| `api.oidc.dex.skipApprovalScreen`           | Whether to skip Dex's own approval screen. Since upstream identity providers will already request user consent, this second approval screen from Dex can be both superfluous and confusing.                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `true`                   |
-| `api.oidc.dex.connectors`                   | Configure [Dex connectors](https://dexidp.io/docs/connectors/) to one or more upstream identity providers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `[]`                     |
-| `api.oidc.dex.resources`                    | Resources limits and requests for the Dex server containers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `{}`                     |
-| `api.oidc.dex.nodeSelector`                 | Node selector for Dex server pods. Defaults to `global.nodeSelector`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `{}`                     |
-| `api.oidc.dex.tolerations`                  | Tolerations for Dex server pods. Defaults to `global.tolerations`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `[]`                     |
-| `api.oidc.dex.affinity`                     | Specifies pod affinity for the Dex server pods. Defaults to `global.affinity`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `{}`                     |
-| `api.oidc.dex.annotations`                  | Annotations to add to the Dex server pods. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `{}`                     |
-| `api.oidc.dex.securityContext`              | Security context for Dex server pods. Defaults to `global.securityContext`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `{}`                     |
-| `api.argocd.urls`                           | Mapping of Argo CD shards names to URLs to support deep links to Argo CD URLs. If sharding is not used, map the empty string to the single Argo CD URL.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `nil`                    |
-| `api.rollouts.integrationEnabled`           | Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. When enabled, the API server will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the API server will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the API server.                                                                                                                    | `true`                   |
-| `api.rollouts.logs.enabled`                 | Specifies whether support for streaming logs from AnalysisRuns using a JobMetric provider is enabled. This feature requires you to have forwarded and stored the logs yourself in a place where they can be retrieved with an HTTP GET.                                                                                                                                                                                                                                                                                                                                                                                                            | `false`                  |
-| `api.rollouts.logs.urlTemplate`             | Instructs Kargo on how to construct a URL for the retrieval of relevant logs via HTTP GET. Expressions offset by ${{ }} are supported with the following variables pre-defined and injected with values: project, namespace (the Project namespace), stage, analysisRun (its name), metricName (name of the JobMetric), jobNamespace (namespace of the Job; may be different that the Project namespace as the Job may actually execute in a different cluster), jobName, container (since a Pod associated with a Job could have more than one). Example: "https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{job}}/${{container}}". | `""`                     |
-| `api.rollouts.logs.tokenSecret.name`        | specifies the name of a Kubernetes Secret managed "out of band" that contains a token usable for accessing job metric logs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `nil`                    |
-| `api.rollouts.logs.tokenSecret.key`         | specifies the key in a Kubernetes Secret (named by name) that is managed "out of band" and contains a token usable for accessing job metric logs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `nil`                    |
-| `api.rollouts.logs.httpHeaders`             | Specifies HTTP headers to include in the HTTP GET request for log retrieval. These are typically used for authentication. The header values support expressions offset by ${{ }}, with the same variables documented for urlTemplate pre-defined and injected with values.                                                                                                                                                                                                                                                                                                                                                                         | `{}`                     |
+| Name                                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Value                    |
+| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
+| `api.enabled`                               | Whether the API server is enabled.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `true`                   |
+| `api.replicas`                              | The number of API server pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `1`                      |
+| `api.host`                                  | The domain name where Kargo's API server will be accessible. When applicable, this is used for generation of an Ingress resource, certificates, and the OpenID Connect issuer and callback URLs. Note: The value in this field MAY include a port number and MUST NOT specify the protocol (http vs https), which is automatically inferred from other configuration options.                                                                                                                                                                                                                                                                                                       | `localhost`              |
+| `api.logLevel`                              | The log level for the API server.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `INFO`                   |
+| `api.labels`                                | Labels to add to the api resources. Merges with `global.labels`, allowing you to override or add to the global labels.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `{}`                     |
+| `api.annotations`                           | Annotations to add to the api resources. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
+| `api.podLabels`                             | Optional labels to add to pods. Merges with `global.podLabels`, allowing you to override or add to the global labels.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
+| `api.podAnnotations`                        | Optional annotations to add to pods. Merges with `global.podAnnotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `{}`                     |
+| `api.secretManagementEnabled`               | Specifies whether Secret management is enabled. This affects the API server's ability to manage repository credentials and other Project-level Secrets, such as those used by AnalysisRuns for verification purposes. If using GitOps to manage Kargo Projects declaratively, the API's Secret management capabilities are not needed and can be disabled to effectively reduce the API server's attackable surface.                                                                                                                                                                                                                                                                | `true`                   |
+| `api.resources`                             | Resources limits and requests for the api containers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
+| `api.nodeSelector`                          | Node selector for api pods. Defaults to `global.nodeSelector`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `{}`                     |
+| `api.tolerations`                           | Tolerations for api pods. Defaults to `global.tolerations`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `[]`                     |
+| `api.affinity`                              | Specifies pod affinity for api pods. Defaults to `global.affinity`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `{}`                     |
+| `api.securityContext`                       | Security context for api pods. Defaults to `global.securityContext`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `{}`                     |
+| `api.cabundle.configMapName`                | Specifies the name of an optional ConfigMap containing CA certs that is managed "out of band." Values in the ConfigMap named here should each contain a single PEM-encoded CA cert. If secretName is also defined, it will take precedence over this field.                                                                                                                                                                                                                                                                                                                                                                                                                         | `""`                     |
+| `api.cabundle.secretName`                   | Specifies the name of an optional Secret containing CA certs that is managed "out of band." Values in the Secret named here should each contain a single PEM-encoded CA cert. If defined, the value of this field takes precedence over any in configMapName.                                                                                                                                                                                                                                                                                                                                                                                                                       | `""`                     |
+| `api.env`                                   | Environment variables to add to API server pods.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `[]`                     |
+| `api.envFrom`                               | Environment variables to add to API server pods from ConfigMaps or Secrets.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `[]`                     |
+| `api.probes.enabled`                        | Whether liveness and readiness probes should be included in the API server deployment. It is sometimes advantageous to disable these during local development.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `true`                   |
+| `api.tls.enabled`                           | Whether to enable TLS directly on the API server. This is helpful if you do not intend to use an ingress controller or if you require TLS end-to-end. All other settings in this section EXCEPT `terminatedUpstream` will be ignored when this is set to `false`.                                                                                                                                                                                                                                                                                                                                                                                                                   | `true`                   |
+| `api.tls.selfSignedCert`                    | Whether to generate a self-signed certificate for use by the API server. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-cert` **must** be provided in the same namespace as Kargo.                                                                                                                                                                                                                                                                                                                                                                              | `true`                   |
+| `api.tls.terminatedUpstream`                | Whether TLS is terminated upstream, i.e. a load balancer, reverse-proxy, or an Ingress controller using a single wildcard cert is terminating it. Setting this to `true` forces all API server URLs to use HTTPS even if the Ingress (if applicable) or API server itself are listening for plain HTTP requests.                                                                                                                                                                                                                                                                                                                                                                    | `false`                  |
+| `api.permissiveCORSPolicyEnabled`           | Whether to enable a permissive CORS (Cross Origin Resource Sharing) policy. This is sometimes advantageous during local development, but otherwise, should generally be left disabled.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `false`                  |
+| `api.ingress.enabled`                       | Whether to enable ingress by creating an Ingress resource. By default, this is disabled. Enabling ingress is advanced usage.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `false`                  |
+| `api.ingress.annotations`                   | Annotations specified by your ingress controller to customize the behavior of the Ingress resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `{}`                     |
+| `api.ingress.ingressClassName`              | If implemented by your ingress controller, specifies the ingress class. If your ingress controller does not support this, use the `kubernetes.io/ingress.class` annotation instead.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `nil`                    |
+| `api.ingress.tls.enabled`                   | Whether to associate a certificate with the Ingress resource.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `true`                   |
+| `api.ingress.tls.selfSignedCert`            | Whether to generate a self-signed certificate for use with the API server's Ingress resource. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-api-ingress-cert` **must** be provided in the same namespace as Kargo. The value in this field has no effect if `api.ingress.tls.enabled` is `false`.                                                                                                                                                                                                                                                                  | `true`                   |
+| `api.ingress.pathType`                      | You may want to use `Prefix` for some controllers (like AWS LoadBalancer Ingress controller), which don't support `/` as wildcard path when pathType is set to `ImplementationSpecific`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `ImplementationSpecific` |
+| `api.service.type`                          | If you're not going to use an ingress controller, you may want to change this value to `LoadBalancer` for production deployments. If running locally, you may want to change it to `NodePort` OR leave it as `ClusterIP` and use `kubectl port-forward` to map a port on the local network interface to the service.                                                                                                                                                                                                                                                                                                                                                                | `ClusterIP`              |
+| `api.service.nodePort`                      | Host port the `Service` will be mapped to when `type` is either `NodePort` or `LoadBalancer`. If not specified, Kubernetes chooses.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `undefined`              |
+| `api.service.annotations`                   | Annotations to add to the API server's service. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `{}`                     |
+| `api.secret.name`                           | Specifies the name of an existing Secret which contains the `ADMIN_ACCOUNT_PASSWORD_HASH` and `ADMIN_ACCOUNT_TOKEN_SIGNING_KEY` values. By setting this, the Secret will **not** be generated by Helm.                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `""`                     |
+| `api.adminAccount.enabled`                  | Whether to enable the admin account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `true`                   |
+| `api.adminAccount.passwordHash`             | Bcrypt password hash for the admin account. A value **must** be provided for this field unless `api.secret.name` is specified.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `""`                     |
+| `api.adminAccount.tokenSigningKey`          | Key used to sign ID tokens (JWTs) for the admin account. It is suggested that you generate this using a password manager or a command like: `openssl rand -base64 29 \| tr -d "=+/" \| cut`. A value **must** be provided for this field, unless `api.secret.name` is specified.                                                                                                                                                                                                                                                                                                                                                                                                    | `""`                     |
+| `api.adminAccount.tokenTTL`                 | Specifies how long ID tokens for the admin account are valid. (i.e. The expiry will be the time of issue plus this duration.)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `24h`                    |
+| `api.oidc.enabled`                          | Whether to enable authentication using Open ID Connect.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `false`                  |
+| `api.oidc.issuerURL`                        | The issuer URL for the identity provider. If Dex is enabled, this value will be ignored and the issuer URL will be automatically configured. If Dex is not enabled, this should be set to the issuer URL provided to you by your identity provider.                                                                                                                                                                                                                                                                                                                                                                                                                                 | `nil`                    |
+| `api.oidc.clientID`                         | The client ID for the OIDC client. If Dex is enabled, this value will be ignored and the client ID will be automatically configured. If Dex is not enabled, this should be set to the client ID provided to you by your identity provider.                                                                                                                                                                                                                                                                                                                                                                                                                                          | `nil`                    |
+| `api.oidc.cliClientID`                      | The client ID for the OIDC client used by CLI (optional). Needed by some OIDC providers (such as Dex) that require a separate Client ID for web app login vs. CLI login (`http://localhost`). If Dex is enabled, this value will be ignored and cli client ID will be automatically configured. If Dex is not enabled, and a different client app is configured for localhost CLI login, this should be the client ID configured in the IdP.                                                                                                                                                                                                                                        | `nil`                    |
+| `api.oidc.additionalScopes`                 | The additional scopes to send to the OIDC provider. This should be set to the scopes you wish to be provided to your identity provider from clients of Kargo, the scopes openid, profile and email are always requested and don't need to be added, this value is intended for any additional ones you require.                                                                                                                                                                                                                                                                                                                                                                     | `["groups"]`             |
+| `api.oidc.usernameClaim`                    | The claim to use as the username for the user.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `email`                  |
+| `api.oidc.admins.claims`                    | Subjects having any of these claims will automatically be Kargo admins.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `{}`                     |
+| `api.oidc.viewers.claims`                   | Subjects having any of these claims will automatically receive read-only access to all Kargo resources.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `{}`                     |
+| `api.oidc.globalServiceAccounts.namespaces` | List of namespaces to look for shared service accounts.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `[]`                     |
+| `api.oidc.dex.enabled`                      | Whether to enable Dex as the identity provider. When set to true, the Kargo installation will include a Dex server and the Kargo API server will be configured to make the /dex endpoint a reverse proxy for the Dex server.                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `false`                  |
+| `api.oidc.dex.image.repository`             | Image repository of Dex                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `ghcr.io/dexidp/dex`     |
+| `api.oidc.dex.image.tag`                    | Image tag for Dex.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `v2.37.0`                |
+| `api.oidc.dex.image.pullPolicy`             | Image pull policy for Dex.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | `IfNotPresent`           |
+| `api.oidc.dex.image.pullSecrets`            | List of imagePullSecrets.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `[]`                     |
+| `api.oidc.dex.env`                          | Environment variables to add to Dex server pods. This is convenient for cases where api.oidc.dex.connectors needs to reference environment variables from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                                                          | `[]`                     |
+| `api.oidc.dex.envFrom`                      | Environment variables to add to Dex server pods from ConfigMaps or Secrets. This is especially convenient for cases where api.oidc.dex.connectors needs to reference environment variables from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                    | `[]`                     |
+| `api.oidc.dex.volumes`                      | Add additional volumes to Dex pods. This is convenient for cases where api.oidc.dex.connectors needs to reference mounted data from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                                                                                | `[]`                     |
+| `api.oidc.dex.volumeMounts`                 | Add additional volume mounts to Dex pods. This is convenient for cases where api.oidc.dex.connectors needs to reference mounted data from a Secret that is managed "out of band" with a secret management solution such as Sealed Secrets.                                                                                                                                                                                                                                                                                                                                                                                                                                          | `nil`                    |
+| `api.oidc.dex.probes.enabled`               | Whether liveness and readiness probes should be included in the Dex server deployment. It is sometimes advantageous to disable these during local development.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `true`                   |
+| `api.oidc.dex.tls.selfSignedCert`           | Whether to generate a self-signed certificate for use with Dex. If `true`, `cert-manager` CRDs **must** be present in the cluster. Kargo will create and use its own namespaced issuer. If `false`, a cert secret named `kargo-dex-server-cert` **must** be provided in the same namespace as Kargo. There is no provision for running Dex without TLS.                                                                                                                                                                                                                                                                                                                             | `true`                   |
+| `api.oidc.dex.skipApprovalScreen`           | Whether to skip Dex's own approval screen. Since upstream identity providers will already request user consent, this second approval screen from Dex can be both superfluous and confusing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `true`                   |
+| `api.oidc.dex.connectors`                   | Configure [Dex connectors](https://dexidp.io/docs/connectors/) to one or more upstream identity providers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | `[]`                     |
+| `api.oidc.dex.resources`                    | Resources limits and requests for the Dex server containers.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `{}`                     |
+| `api.oidc.dex.nodeSelector`                 | Node selector for Dex server pods. Defaults to `global.nodeSelector`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `{}`                     |
+| `api.oidc.dex.tolerations`                  | Tolerations for Dex server pods. Defaults to `global.tolerations`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `[]`                     |
+| `api.oidc.dex.affinity`                     | Specifies pod affinity for the Dex server pods. Defaults to `global.affinity`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | `{}`                     |
+| `api.oidc.dex.annotations`                  | Annotations to add to the Dex server pods. Merges with `global.annotations`, allowing you to override or add to the global annotations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `{}`                     |
+| `api.oidc.dex.securityContext`              | Security context for Dex server pods. Defaults to `global.securityContext`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `{}`                     |
+| `api.argocd.urls`                           | Mapping of Argo CD shards names to URLs to support deep links to Argo CD URLs. If sharding is not used, map the empty string to the single Argo CD URL.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `nil`                    |
+| `api.rollouts.integrationEnabled`           | Specifies whether Argo Rollouts integration is enabled. When not enabled, the API server will not be capable of creating/updating/applying AnalysesTemplate resources in the Kargo control plane. When enabled, the API server will perform a sanity check at startup. If Argo Rollouts CRDs are not found, the API server will proceed as if this integration had been explicitly disabled. Explicitly disabling is still preferable if this integration is not desired, as it will grant fewer permissions to the API server.                                                                                                                                                     | `true`                   |
+| `api.rollouts.logs.enabled`                 | Specifies whether support for streaming logs from AnalysisRuns using a JobMetric provider is enabled. This feature requires you to have forwarded and stored the logs yourself in a place where they can be retrieved with an HTTP GET.                                                                                                                                                                                                                                                                                                                                                                                                                                             | `false`                  |
+| `api.rollouts.logs.urlTemplate`             | Instructs Kargo on how to construct a URL for the retrieval of relevant logs via HTTP GET. Expressions offset by ${{ }} are supported with the following variables pre-defined and injected with values: project (name), namespace (always equal to the Project's name), stage (name), analysisRun (name), metricName (name of the JobMetric), jobNamespace (namespace of the Job; may be different that the Project namespace as the Job may actually execute in a different cluster), jobName, container (name; since a Pod associated with a Job could have more than one). Example: "https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{jobName}}/${{container}}". | `""`                     |
+| `api.rollouts.logs.tokenSecret.name`        | specifies the name of a Kubernetes Secret managed "out of band" that contains a token usable for accessing job metric logs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `nil`                    |
+| `api.rollouts.logs.tokenSecret.key`         | specifies the key in a Kubernetes Secret (named by name) that is managed "out of band" and contains a token usable for accessing job metric logs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `nil`                    |
+| `api.rollouts.logs.httpHeaders`             | Specifies HTTP headers to include in the HTTP GET request for log retrieval. These are typically used for authentication. The header values support expressions offset by ${{ }}, with the same variables documented for urlTemplate pre-defined and injected with values.                                                                                                                                                                                                                                                                                                                                                                                                          | `{}`                     |
 
 ### Controller
 
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_clusterpromotiontasks.yaml b/charts/kargo/resources/crds/kargo.akuity.io_clusterpromotiontasks.yaml
index 73d9b15e..afbd4ed9 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_clusterpromotiontasks.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_clusterpromotiontasks.yaml
@@ -151,8 +151,12 @@ spec:
                         PromotionSpec.
                       items:
                         description: |-
-                          PromotionVariable describes a single variable that may be referenced by
-                          expressions in promotion steps.
+                          ExpressionVariable describes a single variable that may be referenced by
+                          expressions in the context of a ClusterPromotionTask, PromotionTask,
+                          Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                          It is used to pass information to the expression evaluation engine, and to
+                          allow for dynamic evaluation of expressions based on the variable values.
                         properties:
                           name:
                             description: Name is the name of the variable.
@@ -183,8 +187,12 @@ spec:
                   overridden by the step referencing the task.
                 items:
                   description: |-
-                    PromotionVariable describes a single variable that may be referenced by
-                    expressions in promotion steps.
+                    ExpressionVariable describes a single variable that may be referenced by
+                    expressions in the context of a ClusterPromotionTask, PromotionTask,
+                    Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                    It is used to pass information to the expression evaluation engine, and to
+                    allow for dynamic evaluation of expressions based on the variable values.
                   properties:
                     name:
                       description: Name is the name of the variable.
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml b/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
index 89764547..876e1680 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
@@ -183,8 +183,12 @@ spec:
                         PromotionSpec.
                       items:
                         description: |-
-                          PromotionVariable describes a single variable that may be referenced by
-                          expressions in promotion steps.
+                          ExpressionVariable describes a single variable that may be referenced by
+                          expressions in the context of a ClusterPromotionTask, PromotionTask,
+                          Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                          It is used to pass information to the expression evaluation engine, and to
+                          allow for dynamic evaluation of expressions based on the variable values.
                         properties:
                           name:
                             description: Name is the name of the variable.
@@ -214,8 +218,12 @@ spec:
                   promotion steps.
                 items:
                   description: |-
-                    PromotionVariable describes a single variable that may be referenced by
-                    expressions in promotion steps.
+                    ExpressionVariable describes a single variable that may be referenced by
+                    expressions in the context of a ClusterPromotionTask, PromotionTask,
+                    Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                    It is used to pass information to the expression evaluation engine, and to
+                    allow for dynamic evaluation of expressions based on the variable values.
                   properties:
                     name:
                       description: Name is the name of the variable.
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_promotiontasks.yaml b/charts/kargo/resources/crds/kargo.akuity.io_promotiontasks.yaml
index 7a005a84..cb7d0da8 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_promotiontasks.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_promotiontasks.yaml
@@ -151,8 +151,12 @@ spec:
                         PromotionSpec.
                       items:
                         description: |-
-                          PromotionVariable describes a single variable that may be referenced by
-                          expressions in promotion steps.
+                          ExpressionVariable describes a single variable that may be referenced by
+                          expressions in the context of a ClusterPromotionTask, PromotionTask,
+                          Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                          It is used to pass information to the expression evaluation engine, and to
+                          allow for dynamic evaluation of expressions based on the variable values.
                         properties:
                           name:
                             description: Name is the name of the variable.
@@ -183,8 +187,12 @@ spec:
                   overridden by the step referencing the task.
                 items:
                   description: |-
-                    PromotionVariable describes a single variable that may be referenced by
-                    expressions in promotion steps.
+                    ExpressionVariable describes a single variable that may be referenced by
+                    expressions in the context of a ClusterPromotionTask, PromotionTask,
+                    Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                    It is used to pass information to the expression evaluation engine, and to
+                    allow for dynamic evaluation of expressions based on the variable values.
                   properties:
                     name:
                       description: Name is the name of the variable.
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml b/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
index 27c13d18..6edc1de4 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
@@ -173,8 +173,12 @@ spec:
                                 PromotionSpec.
                               items:
                                 description: |-
-                                  PromotionVariable describes a single variable that may be referenced by
-                                  expressions in promotion steps.
+                                  ExpressionVariable describes a single variable that may be referenced by
+                                  expressions in the context of a ClusterPromotionTask, PromotionTask,
+                                  Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                                  It is used to pass information to the expression evaluation engine, and to
+                                  allow for dynamic evaluation of expressions based on the variable values.
                                 properties:
                                   name:
                                     description: Name is the name of the variable.
@@ -204,8 +208,12 @@ spec:
                           promotion steps.
                         items:
                           description: |-
-                            PromotionVariable describes a single variable that may be referenced by
-                            expressions in promotion steps.
+                            ExpressionVariable describes a single variable that may be referenced by
+                            expressions in the context of a ClusterPromotionTask, PromotionTask,
+                            Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                            It is used to pass information to the expression evaluation engine, and to
+                            allow for dynamic evaluation of expressions based on the variable values.
                           properties:
                             name:
                               description: Name is the name of the variable.
@@ -320,6 +328,35 @@ spec:
                   kargo.akuity.io/shard label with the value of this field. When this field
                   is empty, the webhook will ensure that label is absent.
                 type: string
+              vars:
+                description: |-
+                  Vars is a list of variables that can be referenced anywhere in the
+                  StageSpec that supports expressions. For example, the PromotionTemplate
+                  and arguments of the Verification.
+                items:
+                  description: |-
+                    ExpressionVariable describes a single variable that may be referenced by
+                    expressions in the context of a ClusterPromotionTask, PromotionTask,
+                    Promotion, AnalysisRun arguments, or other objects that support expressions.
+
+                    It is used to pass information to the expression evaluation engine, and to
+                    allow for dynamic evaluation of expressions based on the variable values.
+                  properties:
+                    name:
+                      description: Name is the name of the variable.
+                      minLength: 1
+                      pattern: ^[a-zA-Z_]\w*$
+                      type: string
+                    value:
+                      description: |-
+                        Value is the value of the variable. It is allowed to utilize expressions
+                        in the value.
+                        See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
+                      type: string
+                  required:
+                  - name
+                  type: object
+                type: array
               verification:
                 description: |-
                   Verification describes how to verify a Stage's current Freight is fit for
diff --git a/charts/kargo/templates/api/cluster-role.yaml b/charts/kargo/templates/api/cluster-role.yaml
index 0a35ca02..2f51b917 100644
--- a/charts/kargo/templates/api/cluster-role.yaml
+++ b/charts/kargo/templates/api/cluster-role.yaml
@@ -33,22 +33,6 @@ rules:
       - watch
       - create
       - patch
-  - apiGroups:
-      - ""
-    resources:
-      - configmaps
-    verbs:
-      - get
-      - list
-      - watch
-  - apiGroups:
-      - ""
-    resources:
-      - secrets
-    verbs:
-      - get
-      - list
-      - watch
   - apiGroups:
       - authorization.k8s.io
     resources:
diff --git a/charts/kargo/templates/api/configmap.yaml b/charts/kargo/templates/api/configmap.yaml
index ece6b8c6..cc369b68 100644
--- a/charts/kargo/templates/api/configmap.yaml
+++ b/charts/kargo/templates/api/configmap.yaml
@@ -49,6 +49,7 @@ data:
   {{- if .Values.api.oidc.cliClientID }}
   OIDC_CLI_CLIENT_ID: {{ quote .Values.api.oidc.cliClientID }}
   {{- end }}
+  OIDC_USERNAME_CLAIM: {{ quote .Values.api.oidc.usernameClaim | default "email" }}
   {{- end }}
   {{- end }}
   {{- if .Values.api.argocd.urls }}
diff --git a/charts/kargo/values.yaml b/charts/kargo/values.yaml
index de9785dc..35f44c83 100755
--- a/charts/kargo/values.yaml
+++ b/charts/kargo/values.yaml
@@ -211,6 +211,8 @@ api:
     ## @param api.oidc.additionalScopes The additional scopes to send to the OIDC provider. This should be set to the scopes you wish to be provided to your identity provider from clients of Kargo, the scopes openid, profile and email are always requested and don't need to be added, this value is intended for any additional ones you require.
     additionalScopes:
     - groups
+    ## @param api.oidc.usernameClaim The claim to use as the username for the user.
+    usernameClaim: email
 
     admins:
       ## @param api.oidc.admins.claims Subjects having any of these claims will automatically be Kargo admins.
@@ -355,7 +357,7 @@ api:
     logs:
       ## @param api.rollouts.logs.enabled Specifies whether support for streaming logs from AnalysisRuns using a JobMetric provider is enabled. This feature requires you to have forwarded and stored the logs yourself in a place where they can be retrieved with an HTTP GET.
       enabled: false
-      ## @param api.rollouts.logs.urlTemplate Instructs Kargo on how to construct a URL for the retrieval of relevant logs via HTTP GET. Expressions offset by ${{ }} are supported with the following variables pre-defined and injected with values: project, namespace (the Project namespace), stage, analysisRun (its name), metricName (name of the JobMetric), jobNamespace (namespace of the Job; may be different that the Project namespace as the Job may actually execute in a different cluster), jobName, container (since a Pod associated with a Job could have more than one). Example: "https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{job}}/${{container}}".
+      ## @param api.rollouts.logs.urlTemplate Instructs Kargo on how to construct a URL for the retrieval of relevant logs via HTTP GET. Expressions offset by ${{ }} are supported with the following variables pre-defined and injected with values: project (name), namespace (always equal to the Project's name), stage (name), analysisRun (name), metricName (name of the JobMetric), jobNamespace (namespace of the Job; may be different that the Project namespace as the Job may actually execute in a different cluster), jobName, container (name; since a Pod associated with a Job could have more than one). Example: "https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{jobName}}/${{container}}".
       urlTemplate: ""
       tokenSecret:
         ## @param api.rollouts.logs.tokenSecret.name specifies the name of a Kubernetes Secret managed "out of band" that contains a token usable for accessing job metric logs.
diff --git a/docs/docs/40-operator-guide/20-advanced-installation/30-common-configurations.md b/docs/docs/40-operator-guide/20-advanced-installation/30-common-configurations.md
index 2d97df95..afc18a20 100644
--- a/docs/docs/40-operator-guide/20-advanced-installation/30-common-configurations.md
+++ b/docs/docs/40-operator-guide/20-advanced-installation/30-common-configurations.md
@@ -370,8 +370,9 @@ any logs that it can access via an HTTP GET request.
 
 To facilitate this, operators may, at the time of installation, provide a URL
 template that the API server can use to construct the URL for any job metric logs
-as a function of attributes such as `Project` name, `Stage` name, `AnalysisRun`
-name, and more.
+as a function of `project` (name), `namespace` (always equal to the `Project`'s
+name), `shard`, `stage` (name), `analysisRun` (name), `metricName`,
+`jobNamespace`, `jobName`, and `container` (name).
 
 A token can be specified by referencing a Kubernetes `Secret` that is managed
 "out of band." HTTP headers may also be specified, and may reference the token
@@ -385,7 +386,7 @@ api:
     integrationEnabled: true
     logs:
       enabled: true
-      urlTemplate: https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{job}}/${{container}}
+      urlTemplate: https://logs.kargo.example.com/${{project}}/${{analysisRun}}/${{jobName}}/${{container}}
       tokenSecret:
         name: kargo-logs-token
         key: token
diff --git a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/helm-template.md b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/helm-template.md
index eb34bbee..4325b011 100644
--- a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/helm-template.md
+++ b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/helm-template.md
@@ -26,6 +26,9 @@ commonly preceded by a [`git-clear` step](git-clear.md) and followed by
 | `skipTests` | `boolean` | N | Whether to skip tests when rendering the manifests. This is `false` by default. |
 | `kubeVersion` | `string` | N | Optionally specifies a Kubernetes version to be assumed when rendering manifests. This is useful for charts that may contain logic specific to different Kubernetes versions. |
 | `apiVersions` | `[]string` | N | Allows a manual set of supported API versions to be specified. |
+| `setValues` | `[]object` | N | Allows for amending chart configuration inline as one would with the `helm template` command's `--set` flag. |
+| `setValues[].key` | `string` | N | The key whose value should be set. For nested values, use dots to delimit key parts. e.g. `image.tag`. |
+| `setValues[].value` | `string` | N | The new value for the key. |
 
 ## Examples
 
diff --git a/docs/docs/50-user-guide/60-reference-docs/40-expressions.md b/docs/docs/50-user-guide/60-reference-docs/40-expressions.md
index 507b58f2..49decb79 100644
--- a/docs/docs/50-user-guide/60-reference-docs/40-expressions.md
+++ b/docs/docs/50-user-guide/60-reference-docs/40-expressions.md
@@ -286,7 +286,14 @@ The returned `FreightOrigin` object has the following fields:
 
 The `FreightOrigin` object can be used as an optional argument to the
 `commitFrom()`, `imageFrom()`, or `chartFrom()` functions to disambiguate the
-desired source of an artifact when necessary.
+desired source of an artifact when necessary. These functions return `nil` when
+relevant `Freight` is not found from the `FreightCollection`. 
+
+:::tip
+You can handle `nil` values gracefully in Expr using its
+[nil coalescing](https://expr-lang.org/docs/language-definition#nil-coalescing) and
+[optional chaining](https://expr-lang.org/docs/language-definition#optional-chaining) features.
+:::
 
 ### `commitFrom(repoURL, [freightOrigin])`
 
@@ -315,6 +322,8 @@ The optional `freightOrigin` argument should be used when a `Stage` requests
 `Freight` from multiple origins (`Warehouse`s) and more than one can provide a
 `GitCommit` object from the specified repository.
 
+If a commit is not found from the `FreightCollection`, returns `nil`.
+
 Examples:
 
 ```yaml
@@ -352,6 +361,8 @@ The optional `freightOrigin` argument should be used when a `Stage` requests
 `Freight` from multiple origins (`Warehouse`s) and more than one can provide a
 `Image` object from the specified repository.
 
+If an image is not found from the `FreightCollection`, returns `nil`.
+
 Examples:
 
 ```yaml
@@ -396,6 +407,8 @@ The optional `freightOrigin` argument should be used when a `Stage` requests
 `Freight` from multiple origins (`Warehouse`s) and more than one can provide a
 `Chart` object from the specified repository.
 
+If a chart is not found from the `FreightCollection`, returns `nil`.
+
 Examples:
 
 ```yaml
diff --git a/docs/docs/50-user-guide/60-reference-docs/50-analysis-templates.md b/docs/docs/50-user-guide/60-reference-docs/50-analysis-templates.md
index b2d68695..5498c242 100644
--- a/docs/docs/50-user-guide/60-reference-docs/50-analysis-templates.md
+++ b/docs/docs/50-user-guide/60-reference-docs/50-analysis-templates.md
@@ -97,6 +97,40 @@ spec:
         jsonPath: "{$.results.ok}"
 ```
 
+## Interval and Count
+
+To collect multiple measurements over a longer duration, use the 
+`count` and `interval` fields. This allows you to define how many
+measurements to take and how frequently to take them.
+
+In the example below, the analysis is configured to take 20
+measurements at 3-minute intervals, resulting in a total duration
+of approximately 1 hour:
+
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: AnalysisTemplate
+metadata:
+  name: count-interval-example
+spec:
+  metrics:
+  - name: test
+    # Number of measurements to take
+    count: 20
+    # Time interval between each measurement as a duration string
+    interval: 3m
+    provider:
+      web:
+        url: https://pokeapi.co/api/v2/pokemon/pikachu
+```
+
+:::caution
+When specifying an `interval`, you must also specify a `count`. Without 
+it, the AnalysisRun would collect an indefinite amount of measurements
+and never complete (until terminated).
+:::
+
+
 ## Success Condition
 
 When interpreting the result of a query, an
@@ -150,6 +184,7 @@ spec:
   metrics:
   - name: total-errors
     interval: 5m
+    count: 3
     failureCondition: result[0] >= 10
     failureLimit: 3
     provider:
diff --git a/go.mod b/go.mod
index 528beaae..61c61424 100644
--- a/go.mod
+++ b/go.mod
@@ -67,7 +67,7 @@ require (
 	google.golang.org/grpc v1.71.1
 	google.golang.org/protobuf v1.36.6
 	gopkg.in/yaml.v3 v3.0.1
-	helm.sh/helm/v3 v3.17.2
+	helm.sh/helm/v3 v3.17.3
 	k8s.io/api v0.32.3
 	k8s.io/apiextensions-apiserver v0.32.3
 	k8s.io/apimachinery v0.32.3
diff --git a/go.sum b/go.sum
index 40dc1dcd..7d5b7bc9 100644
--- a/go.sum
+++ b/go.sum
@@ -660,8 +660,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o=
-helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
+helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg=
+helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
 k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
 k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
 k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY=
diff --git a/hack/codegen/proto.sh b/hack/codegen/proto.sh
index f6905d07..9b5cce90 100755
--- a/hack/codegen/proto.sh
+++ b/hack/codegen/proto.sh
@@ -20,6 +20,36 @@ readonly APIMACHINERY_PKGS=(
   "-k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
+# Default to cleaning up the temporary directory
+CLEANUP_TMP="true"
+
+# Function to display usage
+function usage() {
+  echo "Usage: $0 [options]"
+  echo "Options:"
+  echo "  --no-cleanup    Don't clean up temporary directory after execution"
+  echo "  -h, --help      Display this help message"
+}
+
+# Parse command line arguments
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --no-cleanup)
+      CLEANUP_TMP="false"
+      shift
+      ;;
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    *)
+      echo "Unknown option: $1"
+      usage
+      exit 1
+      ;;
+  esac
+done
+
 function main() {
   set -x
 
@@ -78,9 +108,9 @@ function main() {
   { msg "Copying generated .proto and .pb.go files back to the project root..."; } 2> /dev/null
   find "$build_src_dir/api" \( \
     -name '*.proto' -o \
-    -name '*.pg.go' \
+    -name '*.pb.go' \
   \) -type f | while read -r file; do
-    rel_path="${file#$build_src_dir/}"
+    rel_path="${file#"$build_src_dir"/}"
     dest_file="$proj_dir/$rel_path"
     dest_dir=$(dirname "$dest_file")
     mkdir -p "$dest_dir"
@@ -138,6 +168,14 @@ function clean() {
   { msg "Cleaning up all intermediate resources..."; } 2> /dev/null
   rm -rf "${proj_dir}/vendor" || true
   rm -rf "${proj_dir}/tmp" || true
+
+  if [[ "${CLEANUP_TMP}" == "true" ]]; then
+    { msg "Cleaning up temporary directory..."; } 2> /dev/null
+    rm -rf "${tmp_dir}" || true
+  else
+    { msg "Temporary directory '${tmp_dir}' preserved"; } 2> /dev/null
+  fi
+
   { msg "Done"; } 2> /dev/null
 }
 
diff --git a/hack/tools/go.mod b/hack/tools/go.mod
index 831c4a05..05b8661f 100644
--- a/hack/tools/go.mod
+++ b/hack/tools/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/bufbuild/buf v1.51.0
 	github.com/golangci/golangci-lint v1.64.8
 	golang.org/x/tools v0.31.0
-	helm.sh/helm/v3 v3.17.2
+	helm.sh/helm/v3 v3.17.3
 	k8s.io/code-generator v0.32.3
 	sigs.k8s.io/controller-tools v0.17.3
 )
diff --git a/hack/tools/go.sum b/hack/tools/go.sum
index d934f44d..2c81adee 100644
--- a/hack/tools/go.sum
+++ b/hack/tools/go.sum
@@ -1032,8 +1032,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
 gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
-helm.sh/helm/v3 v3.17.2 h1:agYQ5ew2jq5vdx2K7q5W44KyKQrnSubUMCQsjkiv3/o=
-helm.sh/helm/v3 v3.17.2/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
+helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg=
+helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8=
 honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
 honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
 k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
diff --git a/internal/api/event.go b/internal/api/event.go
index 02d183d3..ce8d250a 100644
--- a/internal/api/event.go
+++ b/internal/api/event.go
@@ -1,6 +1,8 @@
 package api
 
 import (
+	"fmt"
+	"os"
 	"time"
 
 	authnv1 "k8s.io/api/authentication/v1"
@@ -21,28 +23,23 @@ func FormatEventControllerActor(name string) string {
 // 3. If the subject is available, it returns subject in "subject:<subject>" format.
 // 4. Otherwise, it returns EventActorUnknown.
 func FormatEventUserActor(u user.Info) string {
-	var email, subject string
+	if u.IsAdmin {
+		return kargoapi.EventActorAdmin
+	}
+	if u.Username != "" {
+		return formatOIDCUsername(u.Username)
+	}
 	if emailClaim, ok := u.Claims["email"]; ok {
-		if emailStr, ok := emailClaim.(string); ok {
-			email = emailStr
+		if email, ok := emailClaim.(string); ok {
+			return kargoapi.EventActorEmailPrefix + email
 		}
 	}
 	if subClaim, ok := u.Claims["sub"]; ok {
-		if subStr, ok := subClaim.(string); ok {
-			subject = subStr
+		if sub, ok := subClaim.(string); ok {
+			return kargoapi.EventActorSubjectPrefix + sub
 		}
 	}
-
-	switch {
-	case u.IsAdmin:
-		return kargoapi.EventActorAdmin
-	case email != "":
-		return kargoapi.EventActorEmailPrefix + email
-	case subject != "":
-		return kargoapi.EventActorSubjectPrefix + subject
-	default:
-		return kargoapi.EventActorUnknown
-	}
+	return kargoapi.EventActorUnknown
 }
 
 func FormatEventKubernetesUserActor(u authnv1.UserInfo) string {
@@ -62,3 +59,7 @@ func NewFreightApprovedEventAnnotations(actor string, f *kargoapi.Freight, stage
 	}
 	return annotations
 }
+
+func formatOIDCUsername(oidcUsername string) string {
+	return fmt.Sprintf("%s:%s", os.Getenv("OIDC_USERNAME_CLAIM"), oidcUsername)
+}
diff --git a/internal/controller/freight/finder.go b/internal/controller/freight/finder.go
index 2a4c0127..49b509b0 100644
--- a/internal/controller/freight/finder.go
+++ b/internal/controller/freight/finder.go
@@ -73,9 +73,7 @@ func FindCommit(
 		}
 	}
 	if desiredOrigin == nil {
-		return nil, NotFoundError{
-			msg: fmt.Sprintf("commit from repo %s not found in referenced Freight", repoURL),
-		}
+		return nil, nil
 	}
 	// We know exactly what we're after, so this should be easy
 	for i := range freight {
@@ -92,9 +90,7 @@ func FindCommit(
 	// If we get to here, we looked at all the FreightReferences and didn't find
 	// any that came from the desired origin. This could be because no Freight
 	// from the desired origin has been promoted yet.
-	return nil, NotFoundError{
-		msg: fmt.Sprintf("commit from repo %s not found in referenced Freight", repoURL),
-	}
+	return nil, nil
 }
 
 func FindImage(
@@ -147,9 +143,7 @@ func FindImage(
 	if desiredOrigin == nil {
 		// There is no chance of finding the commit we're looking for. Just return
 		// nil and let the caller decide what to do.
-		return nil, NotFoundError{
-			msg: fmt.Sprintf("image from repo %s not found in referenced Freight", repoURL),
-		}
+		return nil, nil
 	}
 	// We know exactly what we're after, so this should be easy
 	for _, f := range freight {
@@ -163,10 +157,9 @@ func FindImage(
 	}
 	// If we get to here, we looked at all the FreightReferences and didn't find
 	// any that came from the desired origin. This could be because no Freight
-	// from the desired origin has been promoted yet.
-	return nil, NotFoundError{
-		msg: fmt.Sprintf("image from repo %s not found in referenced Freight", repoURL),
-	}
+	// from the desired origin has been promoted yet. We return nil to indicate
+	// that none was found
+	return nil, nil
 }
 
 func HasAmbiguousImageRequest(
@@ -285,12 +278,5 @@ func FindChart(
 	// If we get to here, we looked at all the FreightReferences and didn't find
 	// any that came from the desired origin. This could be because no Freight
 	// from the desired origin has been promoted yet.
-	if chartName == "" {
-		return nil, NotFoundError{
-			msg: fmt.Sprintf("chart from repo %s not found in referenced Freight", repoURL),
-		}
-	}
-	return nil, NotFoundError{
-		msg: fmt.Sprintf("chart %q from repo %s not found in referenced Freight", chartName, repoURL),
-	}
+	return nil, nil
 }
diff --git a/internal/controller/promotions/promotions.go b/internal/controller/promotions/promotions.go
index d6540e50..97230d45 100644
--- a/internal/controller/promotions/promotions.go
+++ b/internal/controller/promotions/promotions.go
@@ -104,7 +104,7 @@ func SetupReconcilerWithManager(
 		ctx,
 		&kargoapi.Promotion{},
 		indexer.RunningPromotionsByArgoCDApplicationsField,
-		indexer.RunningPromotionsByArgoCDApplications(ctx, cfg.ShardName),
+		indexer.RunningPromotionsByArgoCDApplications(ctx, kargoMgr.GetClient(), cfg.ShardName),
 	); err != nil {
 		return fmt.Errorf("index running Promotions by Argo CD Applications: %w", err)
 	}
diff --git a/internal/indexer/indexer.go b/internal/indexer/indexer.go
index c2aef870..c33255fd 100644
--- a/internal/indexer/indexer.go
+++ b/internal/indexer/indexer.go
@@ -114,6 +114,7 @@ func PromotionsByStage(obj client.Object) []string {
 // Promotions not labeled with a shardName are indexed.
 func RunningPromotionsByArgoCDApplications(
 	ctx context.Context,
+	cl client.Client,
 	shardName string,
 ) client.IndexerFunc {
 	logger := logging.LoggerFromContext(ctx)
@@ -176,7 +177,7 @@ func RunningPromotionsByArgoCDApplications(
 
 			// As step-level variables are allowed to reference to output, we
 			// need to provide the state.
-			vars, err := dirStep.GetVars(promoCtx, promoCtx.State)
+			vars, err := dirStep.GetVars(ctx, cl, promoCtx, promoCtx.State)
 			if err != nil {
 				logger.Error(
 					err,
diff --git a/internal/kargo/promotion_builder.go b/internal/kargo/promotion_builder.go
index c0dc6927..a368bf66 100644
--- a/internal/kargo/promotion_builder.go
+++ b/internal/kargo/promotion_builder.go
@@ -71,6 +71,11 @@ func (b *PromotionBuilder) Build(
 		annotations[kargoapi.AnnotationKeyCreateActor] = api.FormatEventUserActor(u)
 	}
 
+	// Merge Stage variables with PromotionTemplate variables
+	vars := make([]kargoapi.ExpressionVariable, 0, len(stage.Spec.Vars)+len(stage.Spec.PromotionTemplate.Spec.Vars))
+	vars = append(vars, stage.Spec.Vars...)
+	vars = append(vars, stage.Spec.PromotionTemplate.Spec.Vars...)
+
 	promotion := kargoapi.Promotion{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:        generatePromotionName(stage.Name, freight),
@@ -80,7 +85,7 @@ func (b *PromotionBuilder) Build(
 		Spec: kargoapi.PromotionSpec{
 			Stage:   stage.Name,
 			Freight: freight,
-			Vars:    stage.Spec.PromotionTemplate.Spec.Vars,
+			Vars:    vars,
 			Steps:   stage.Spec.PromotionTemplate.Spec.Steps,
 		},
 	}
@@ -121,7 +126,7 @@ func (b *PromotionBuilder) InflateSteps(ctx context.Context, promo *kargoapi.Pro
 func (b *PromotionBuilder) inflateTaskSteps(
 	ctx context.Context,
 	project, taskAlias string,
-	promoVars []kargoapi.PromotionVariable,
+	promoVars []kargoapi.ExpressionVariable,
 	taskStep kargoapi.PromotionStep,
 ) ([]kargoapi.PromotionStep, error) {
 	task, err := b.getTaskSpec(ctx, project, taskStep.Task)
@@ -220,8 +225,8 @@ func generatePromotionTaskStepAlias(taskAlias, stepAlias string) string {
 // variables and maps them to variables which can be used by the inflated
 // PromotionStep.
 func promotionTaskVarsToStepVars(
-	taskVars, promoVars, stepVars []kargoapi.PromotionVariable,
-) ([]kargoapi.PromotionVariable, error) {
+	taskVars, promoVars, stepVars []kargoapi.ExpressionVariable,
+) ([]kargoapi.ExpressionVariable, error) {
 	// Promotion variables can be used to set (or override) the variables
 	// required by the PromotionTask, but they are not inflated into the
 	// variables for the step. This map is used to check if a variable is
@@ -244,7 +249,7 @@ func promotionTaskVarsToStepVars(
 		}
 	}
 
-	var vars []kargoapi.PromotionVariable
+	var vars []kargoapi.ExpressionVariable
 
 	// Set the PromotionTask variable default values, but only if the variable
 	// is not set on the Promotion.
diff --git a/internal/promotion/promotion.go b/internal/promotion/promotion.go
index fe4f34f8..c19163ef 100644
--- a/internal/promotion/promotion.go
+++ b/internal/promotion/promotion.go
@@ -54,7 +54,7 @@ type Context struct {
 	State promotion.State
 	// Vars is a list of variables definitions that can be used by the
 	// Steps.
-	Vars []kargoapi.PromotionVariable
+	Vars []kargoapi.ExpressionVariable
 	// Secrets is a map of secrets that can be used by the Steps.
 	Secrets map[string]map[string]string
 	// Actor is the name of the actor triggering the Promotion.
@@ -81,7 +81,7 @@ type Step struct {
 	Retry *kargoapi.PromotionStepRetry
 	// Vars is a list of variables definitions that can be used by the
 	// Step.
-	Vars []kargoapi.PromotionVariable
+	Vars []kargoapi.ExpressionVariable
 	// Config is an opaque JSON to be passed to the StepRunner executing this
 	// step.
 	Config []byte
@@ -186,12 +186,17 @@ func (s *Step) BuildEnv(promoCtx Context, opts ...StepEnvOption) map[string]any
 
 // Skip returns true if the Step should be skipped based on the If condition.
 // The If condition is evaluated against the provided Context and State.
-func (s *Step) Skip(promoCtx Context, state promotion.State) (bool, error) {
+func (s *Step) Skip(
+	ctx context.Context,
+	cl client.Client,
+	promoCtx Context,
+	state promotion.State,
+) (bool, error) {
 	if s.If == "" {
 		return false, nil
 	}
 
-	vars, err := s.GetVars(promoCtx, state)
+	vars, err := s.GetVars(ctx, cl, promoCtx, state)
 	if err != nil {
 		return false, err
 	}
@@ -203,7 +208,13 @@ func (s *Step) Skip(promoCtx Context, state promotion.State) (bool, error) {
 		StepEnvWithVars(vars),
 	)
 
-	v, err := expressions.EvaluateTemplate(s.If, env)
+	v, err := expressions.EvaluateTemplate(s.If, env, exprfn.FreightOperations(
+		ctx,
+		cl,
+		promoCtx.Project,
+		promoCtx.FreightRequests,
+		promoCtx.Freight.References(),
+	)...)
 	if err != nil {
 		return false, err
 	}
@@ -228,7 +239,7 @@ func (s *Step) GetConfig(
 		return nil, nil
 	}
 
-	vars, err := s.GetVars(promoCtx, state)
+	vars, err := s.GetVars(ctx, cl, promoCtx, state)
 	if err != nil {
 		return nil, err
 	}
@@ -264,7 +275,12 @@ func (s *Step) GetConfig(
 
 // GetVars returns the variables defined in the Step. The variables are
 // evaluated against the provided Context.
-func (s *Step) GetVars(promoCtx Context, state promotion.State) (map[string]any, error) {
+func (s *Step) GetVars(
+	ctx context.Context,
+	cl client.Client,
+	promoCtx Context,
+	state promotion.State,
+) (map[string]any, error) {
 	vars := make(map[string]any)
 
 	// Evaluate the global variables defined in the Promotion itself, these
@@ -273,6 +289,7 @@ func (s *Step) GetVars(promoCtx Context, state promotion.State) (map[string]any,
 		newVar, err := expressions.EvaluateTemplate(
 			v.Value,
 			s.BuildEnv(promoCtx, StepEnvWithVars(vars)),
+			exprfn.FreightOperations(ctx, cl, promoCtx.Project, promoCtx.FreightRequests, promoCtx.Freight.References())...,
 		)
 		if err != nil {
 			return nil, fmt.Errorf("error pre-processing promotion variable %q: %w", v.Name, err)
@@ -291,6 +308,7 @@ func (s *Step) GetVars(promoCtx Context, state promotion.State) (map[string]any,
 				StepEnvWithTaskOutputs(s.Alias, state),
 				StepEnvWithVars(vars),
 			),
+			exprfn.FreightOperations(ctx, cl, promoCtx.Project, promoCtx.FreightRequests, promoCtx.Freight.References())...,
 		)
 		if err != nil {
 			return nil, fmt.Errorf("error pre-processing promotion variable %q: %w", v.Name, err)
diff --git a/internal/promotion/runner/builtin/helm_template_runner.go b/internal/promotion/runner/builtin/helm_template_runner.go
index 727740e4..4b7dc256 100644
--- a/internal/promotion/runner/builtin/helm_template_runner.go
+++ b/internal/promotion/runner/builtin/helm_template_runner.go
@@ -81,7 +81,7 @@ func (h *helmTemplateRunner) run(
 	stepCtx *promotion.StepContext,
 	cfg builtin.HelmTemplateConfig,
 ) (promotion.StepResult, error) {
-	composedValues, err := h.composeValues(stepCtx.WorkDir, cfg.ValuesFiles)
+	composedValues, err := h.composeValues(stepCtx.WorkDir, cfg)
 	if err != nil {
 		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
 			fmt.Errorf("failed to compose values: %w", err)
@@ -123,17 +123,26 @@ func (h *helmTemplateRunner) run(
 	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
 }
 
-// composeValues composes the values from the given values files. It merges the
-// values in the order they are provided.
-func (h *helmTemplateRunner) composeValues(workDir string, valuesFiles []string) (map[string]any, error) {
+// composeValues composes the values from the given values files and set values.
+// It merges the value files in the order they are provided. Set values are merged
+// into last as overrides
+func (h *helmTemplateRunner) composeValues(
+	workDir string,
+	cfg builtin.HelmTemplateConfig,
+) (map[string]any, error) {
 	valueOpts := &values.Options{}
-	for _, p := range valuesFiles {
+	for _, p := range cfg.ValuesFiles {
 		absValuesPath, err := securejoin.SecureJoin(workDir, p)
 		if err != nil {
 			return nil, fmt.Errorf("failed to join path %q: %w", p, err)
 		}
 		valueOpts.ValueFiles = append(valueOpts.ValueFiles, absValuesPath)
 	}
+	setValueStrVal := make([]string, len(cfg.SetValues))
+	for i, setValue := range cfg.SetValues {
+		setValueStrVal[i] = fmt.Sprintf("%s=%s", setValue.Key, setValue.Value)
+	}
+	valueOpts.Values = append(valueOpts.Values, setValueStrVal...)
 	return valueOpts.MergeValues(nil)
 }
 
diff --git a/internal/promotion/runner/builtin/schemas/helm-template-config.json b/internal/promotion/runner/builtin/schemas/helm-template-config.json
index 69aecdfe..149d9717 100644
--- a/internal/promotion/runner/builtin/schemas/helm-template-config.json
+++ b/internal/promotion/runner/builtin/schemas/helm-template-config.json
@@ -1,6 +1,26 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
   "title": "HelmTemplateConfig",
+
+  "definitions": {
+    "setValues": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "key": {
+          "type": "string",
+          "description": "The key whose value should be set. For nested values, use dots to delimit key parts. e.g. `image.tag`.",
+          "minLength": 1
+        },
+        "value": {
+          "type": "string",
+          "description": "The new value for the key."
+        }
+      },
+      "required": ["key", "value"]
+    }
+  },
+
   "type": "object",
   "additionalProperties": false,
   "required": ["outPath", "path", "releaseName"],
@@ -37,6 +57,13 @@
         "minLength": 1
       }
     },
+    "setValues": {
+      "type": "array",
+      "description": "Allows for amending chart configuration inline as one would with the `helm template` command's `--set` flag.",
+      "items": {
+        "$ref": "#/definitions/setValues"
+      }
+    },
     "includeCRDs": {
       "type": "boolean",
       "description": "Whether to include CRDs in the rendered manifests.",
diff --git a/internal/promotion/simple_engine.go b/internal/promotion/simple_engine.go
index a5377acc..2b7491e2 100644
--- a/internal/promotion/simple_engine.go
+++ b/internal/promotion/simple_engine.go
@@ -143,7 +143,7 @@ func (e *simpleEngine) executeSteps(
 
 		// Check if the step should be skipped.
 		var skip bool
-		if skip, err = step.Skip(promoCtx, state); err != nil {
+		if skip, err = step.Skip(ctx, e.kargoClient, promoCtx, state); err != nil {
 			return Result{
 				Status:                kargoapi.PromotionPhaseErrored,
 				CurrentStep:           i,
diff --git a/internal/rollouts/analysis_run_builder.go b/internal/rollouts/analysis_run_builder.go
index b08a135a..2d0b3526 100644
--- a/internal/rollouts/analysis_run_builder.go
+++ b/internal/rollouts/analysis_run_builder.go
@@ -24,6 +24,9 @@ import (
 // controller instance.
 const controllerInstanceIDLabelKey = "argo-rollouts.argoproj.io/controller-instance-id"
 
+// varsEnvKey is the key for variables in the ArgumentEvaluationConfig.Env map.
+const varsEnvKey = "vars"
+
 // Config holds the configuration for the AnalysisRunBuilder.
 type Config struct {
 	// ControllerInstanceID is the unique identifier for the Argo Rollouts
@@ -192,6 +195,28 @@ func (b *AnalysisRunBuilder) buildArgs(
 		exprCfg = &ArgumentEvaluationConfig{}
 	}
 
+	if len(exprCfg.Vars) > 0 {
+		if exprCfg.Env == nil {
+			exprCfg.Env = make(map[string]any)
+		}
+		if exprCfg.Env[varsEnvKey] == nil {
+			exprCfg.Env[varsEnvKey] = make(map[string]any)
+		}
+		// NB: This will cause direct modifications to the map in the config.
+		vars, ok := exprCfg.Env[varsEnvKey].(map[string]any)
+		if !ok {
+			return nil, fmt.Errorf("vars in argument evaluation config env is of unexpected type %T", exprCfg.Env["vars"])
+		}
+
+		for _, v := range exprCfg.Vars {
+			value, err := expressions.EvaluateTemplate(v.Value, exprCfg.Env, exprCfg.Options...)
+			if err != nil {
+				return nil, fmt.Errorf("evaluate variable %q: %w", v.Name, err)
+			}
+			vars[v.Name] = value
+		}
+	}
+
 	rolloutsArgs := make([]rolloutsapi.Argument, len(args))
 	for i, arg := range args {
 		rolloutsArgs[i] = rolloutsapi.Argument{
diff --git a/internal/rollouts/options.go b/internal/rollouts/options.go
index a26d6aa3..80b52b42 100644
--- a/internal/rollouts/options.go
+++ b/internal/rollouts/options.go
@@ -45,19 +45,41 @@ type Owner struct {
 }
 
 // ArgumentEvaluationConfig holds the configuration for the evaluation of
-// expressions in the AnalysisRun arguments.
+// expressions in the AnalysisRun arguments. It contains the environment
+// variables, the variables, and the options for the expression evaluation.
+//
+// Note: The builder may modify the Env map to add additional information.
+// For example, to add any evaluated Vars. Because of this, it is recommended
+// not to reuse the same ArgumentEvaluationConfig or Env map for multiple
+// builds without making use of a deep copy.
 type ArgumentEvaluationConfig struct {
 	// Env is a (nested) map of variables that can be used in the expressions.
 	// The keys are the variable names, and the values are the variable values.
 	// When the value itself is a map, it is considered a nested variable and
 	// can be accessed using dot notation. e.g. `${{ foo.bar }}`.
+	//
+	// Note: The Env map can be modified by the builder to add additional
+	// information. For example, to add any evaluated Vars.
 	Env map[string]any
+	// Vars are the variables that can be used in the expressions. They are
+	// evaluated in the order they are defined. After the evaluation of the
+	// variables, they are available in the Env map as `${{ vars.<name>}}`.
+	Vars []ArgumentVariable
 	// Options are the options for the expression evaluation. It can be used to
 	// configure the behavior of the expression evaluation and the functions
 	// available.
 	Options []expr.Option
 }
 
+// ArgumentVariable represents a variable that can be used in the AnalysisRun
+// arguments.
+type ArgumentVariable struct {
+	// Name is the name of the variable.
+	Name string
+	// Value is the value of the variable.
+	Value string
+}
+
 // Apply applies the given options to the AnalysisRunOptions.
 func (o *AnalysisRunOptions) Apply(opts ...AnalysisRunOption) {
 	for _, opt := range opts {
diff --git a/internal/server/oidc/config.go b/internal/server/oidc/config.go
index 1a67e88b..76804202 100644
--- a/internal/server/oidc/config.go
+++ b/internal/server/oidc/config.go
@@ -22,6 +22,8 @@ type Config struct {
 	// AdditionalScopes are any more scopes to be requested during the authorization code flow
 	// on top of the default Scopes.
 	AdditionalScopes []string `envconfig:"OIDC_ADDITIONAL_SCOPES"`
+	// UsernameClaim is the claim to use as the username for the user.
+	UsernameClaim string `envconfig:"OIDC_USERNAME_CLAIM" default:"email"`
 
 	// GlobalServiceAccountNamespaces is the list of namespaces to look up
 	// for shared service accounts.
diff --git a/internal/server/option/auth.go b/internal/server/option/auth.go
index 085c09be..d648b756 100644
--- a/internal/server/option/auth.go
+++ b/internal/server/option/auth.go
@@ -398,12 +398,23 @@ func (a *authInterceptor) authenticate(
 		if err != nil {
 			return ctx, fmt.Errorf("list service accounts for user: %w", err)
 		}
+		var username string
+		if un, ok := c[a.cfg.OIDCConfig.UsernameClaim]; ok {
+			if username, ok = un.(string); !ok {
+				return ctx, fmt.Errorf(
+					"claim %q must be a string; got %T",
+					a.cfg.OIDCConfig.UsernameClaim,
+					un,
+				)
+			}
+		}
 		return user.ContextWithInfo(
 			ctx,
 			user.Info{
 				Claims:                     c,
 				ServiceAccountsByNamespace: sa,
 				BearerToken:                rawToken,
+				Username:                   username,
 			},
 		), nil
 
diff --git a/internal/server/user/user.go b/internal/server/user/user.go
index 0f9774eb..bd19a82d 100644
--- a/internal/server/user/user.go
+++ b/internal/server/user/user.go
@@ -27,6 +27,9 @@ type Info struct {
 	// ServiceAccountsByNamespace is the mapping of namespace names to sets of
 	// ServiceAccounts that a user has been mapped to.
 	ServiceAccountsByNamespace map[string]map[types.NamespacedName]struct{}
+	// Username is the username of the user. This is often the email address
+	// of the user, but may be different depending on configuration.
+	Username string
 }
 
 // ContextWithInfo returns a context.Context that has been augmented with
diff --git a/pkg/x/promotion/runner/builtin/zz_config_types.go b/pkg/x/promotion/runner/builtin/zz_config_types.go
index c95e938c..ba4f7071 100644
--- a/pkg/x/promotion/runner/builtin/zz_config_types.go
+++ b/pkg/x/promotion/runner/builtin/zz_config_types.go
@@ -231,6 +231,9 @@ type HelmTemplateConfig struct {
 	Path string `json:"path"`
 	// ReleaseName to use for the rendered manifests.
 	ReleaseName string `json:"releaseName"`
+	// Allows for amending chart configuration inline as one would with the `helm template`
+	// command's `--set` flag.
+	SetValues []SetValues `json:"setValues,omitempty"`
 	// Whether to skip tests when rendering the manifests.
 	SkipTests bool `json:"skipTests,omitempty"`
 	// Whether to use the release name in the output path (instead of the chart name). This only
@@ -240,6 +243,14 @@ type HelmTemplateConfig struct {
 	ValuesFiles []string `json:"valuesFiles,omitempty"`
 }
 
+type SetValues struct {
+	// The key whose value should be set. For nested values, use dots to delimit key parts. e.g.
+	// `image.tag`.
+	Key string `json:"key"`
+	// The new value for the key.
+	Value string `json:"value"`
+}
+
 type HelmUpdateChartConfig struct {
 	// A list of chart dependencies which should receive updates.
 	Charts []Chart `json:"charts"`
diff --git a/ui/package.json b/ui/package.json
index 61372929..aa0bbb3b 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -16,6 +16,8 @@
   "devDependencies": {
     "@apidevtools/json-schema-ref-parser": "^11.9.3",
     "@eslint/compat": "^1.2.7",
+    "@eslint/eslintrc": "^3.3.0",
+    "@eslint/js": "^9.22.0",
     "@openapi-contrib/openapi-schema-to-json-schema": "^5.1.0",
     "@tanstack/react-query-devtools": "^5.71.0",
     "@types/git-url-parse": "^9.0.3",
@@ -43,7 +45,7 @@
     "prettier": "^3.5.3",
     "tailwindcss": "^3.4.17",
     "typescript": "^5.8.2",
-    "vite": "^6.2.4",
+    "vite": "^6.2.5",
     "vite-plugin-compression": "^0.5.1",
     "vite-tsconfig-paths": "^5.1.4",
     "vitest": "^3.0.9",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index f0e9affa..4c774820 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -123,6 +123,12 @@ importers:
       '@eslint/compat':
         specifier: ^1.2.7
         version: 1.2.7(eslint@9.23.0(jiti@1.21.7))
+      '@eslint/eslintrc':
+        specifier: ^3.3.0
+        version: 3.3.1
+      '@eslint/js':
+        specifier: ^9.22.0
+        version: 9.23.0
       '@openapi-contrib/openapi-schema-to-json-schema':
         specifier: ^5.1.0
         version: 5.1.0
@@ -158,7 +164,7 @@ importers:
         version: 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2)
       '@vitejs/plugin-react':
         specifier: ^4.3.4
-        version: 4.3.4(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
+        version: 4.3.4(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
       autoprefixer:
         specifier: ^10.4.21
         version: 10.4.21(postcss@8.5.3)
@@ -205,14 +211,14 @@ importers:
         specifier: ^5.8.2
         version: 5.8.2
       vite:
-        specifier: ^6.2.4
-        version: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+        specifier: ^6.2.5
+        version: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
       vite-plugin-compression:
         specifier: ^0.5.1
-        version: 0.5.1(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
+        version: 0.5.1(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
       vite-tsconfig-paths:
         specifier: ^5.1.4
-        version: 5.1.4(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
+        version: 5.1.4(typescript@5.8.2)(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
       vitest:
         specifier: ^3.0.9
         version: 3.0.9(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
@@ -788,103 +794,103 @@ packages:
     peerDependencies:
       '@rjsf/utils': ^5.24.x
 
-  '@rollup/rollup-android-arm-eabi@4.38.0':
-    resolution: {integrity: sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==}
+  '@rollup/rollup-android-arm-eabi@4.39.0':
+    resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.38.0':
-    resolution: {integrity: sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==}
+  '@rollup/rollup-android-arm64@4.39.0':
+    resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.38.0':
-    resolution: {integrity: sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==}
+  '@rollup/rollup-darwin-arm64@4.39.0':
+    resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.38.0':
-    resolution: {integrity: sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==}
+  '@rollup/rollup-darwin-x64@4.39.0':
+    resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-freebsd-arm64@4.38.0':
-    resolution: {integrity: sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==}
+  '@rollup/rollup-freebsd-arm64@4.39.0':
+    resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==}
     cpu: [arm64]
     os: [freebsd]
 
-  '@rollup/rollup-freebsd-x64@4.38.0':
-    resolution: {integrity: sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==}
+  '@rollup/rollup-freebsd-x64@4.39.0':
+    resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==}
     cpu: [x64]
     os: [freebsd]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
-    resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.39.0':
+    resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.38.0':
-    resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==}
+  '@rollup/rollup-linux-arm-musleabihf@4.39.0':
+    resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.38.0':
-    resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==}
+  '@rollup/rollup-linux-arm64-gnu@4.39.0':
+    resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.38.0':
-    resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==}
+  '@rollup/rollup-linux-arm64-musl@4.39.0':
+    resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
-    resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==}
+  '@rollup/rollup-linux-loongarch64-gnu@4.39.0':
+    resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==}
     cpu: [loong64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
-    resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.39.0':
+    resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.38.0':
-    resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==}
+  '@rollup/rollup-linux-riscv64-gnu@4.39.0':
+    resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-musl@4.38.0':
-    resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==}
+  '@rollup/rollup-linux-riscv64-musl@4.39.0':
+    resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.38.0':
-    resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==}
+  '@rollup/rollup-linux-s390x-gnu@4.39.0':
+    resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.38.0':
-    resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==}
+  '@rollup/rollup-linux-x64-gnu@4.39.0':
+    resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.38.0':
-    resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==}
+  '@rollup/rollup-linux-x64-musl@4.39.0':
+    resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.38.0':
-    resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==}
+  '@rollup/rollup-win32-arm64-msvc@4.39.0':
+    resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.38.0':
-    resolution: {integrity: sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==}
+  '@rollup/rollup-win32-ia32-msvc@4.39.0':
+    resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.38.0':
-    resolution: {integrity: sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==}
+  '@rollup/rollup-win32-x64-msvc@4.39.0':
+    resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==}
     cpu: [x64]
     os: [win32]
 
@@ -3029,8 +3035,8 @@ packages:
     resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
-  rollup@4.38.0:
-    resolution: {integrity: sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==}
+  rollup@4.39.0:
+    resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -3393,8 +3399,8 @@ packages:
       vite:
         optional: true
 
-  vite@6.2.4:
-    resolution: {integrity: sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==}
+  vite@6.2.5:
+    resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==}
     engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
     peerDependencies:
@@ -4118,64 +4124,64 @@ snapshots:
       lodash: 4.17.21
       lodash-es: 4.17.21
 
-  '@rollup/rollup-android-arm-eabi@4.38.0':
+  '@rollup/rollup-android-arm-eabi@4.39.0':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.38.0':
+  '@rollup/rollup-android-arm64@4.39.0':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.38.0':
+  '@rollup/rollup-darwin-arm64@4.39.0':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.38.0':
+  '@rollup/rollup-darwin-x64@4.39.0':
     optional: true
 
-  '@rollup/rollup-freebsd-arm64@4.38.0':
+  '@rollup/rollup-freebsd-arm64@4.39.0':
     optional: true
 
-  '@rollup/rollup-freebsd-x64@4.38.0':
+  '@rollup/rollup-freebsd-x64@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.38.0':
+  '@rollup/rollup-linux-arm-gnueabihf@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.38.0':
+  '@rollup/rollup-linux-arm-musleabihf@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.38.0':
+  '@rollup/rollup-linux-arm64-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.38.0':
+  '@rollup/rollup-linux-arm64-musl@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-loongarch64-gnu@4.38.0':
+  '@rollup/rollup-linux-loongarch64-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.38.0':
+  '@rollup/rollup-linux-riscv64-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-musl@4.38.0':
+  '@rollup/rollup-linux-riscv64-musl@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.38.0':
+  '@rollup/rollup-linux-s390x-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.38.0':
+  '@rollup/rollup-linux-x64-gnu@4.39.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.38.0':
+  '@rollup/rollup-linux-x64-musl@4.39.0':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.38.0':
+  '@rollup/rollup-win32-arm64-msvc@4.39.0':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.38.0':
+  '@rollup/rollup-win32-ia32-msvc@4.39.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.38.0':
+  '@rollup/rollup-win32-x64-msvc@4.39.0':
     optional: true
 
   '@rtsao/scc@1.1.0': {}
@@ -4374,14 +4380,14 @@ snapshots:
       '@typescript-eslint/types': 8.28.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-react@4.3.4(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))':
+  '@vitejs/plugin-react@4.3.4(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))':
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0)
       '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0)
       '@types/babel__core': 7.20.5
       react-refresh: 0.14.2
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -4392,13 +4398,13 @@ snapshots:
       chai: 5.2.0
       tinyrainbow: 2.0.0
 
-  '@vitest/mocker@3.0.9(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))':
+  '@vitest/mocker@3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))':
     dependencies:
       '@vitest/spy': 3.0.9
       estree-walker: 3.0.3
       magic-string: 0.30.17
     optionalDependencies:
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
 
   '@vitest/pretty-format@3.0.9':
     dependencies:
@@ -6706,30 +6712,30 @@ snapshots:
 
   reusify@1.0.4: {}
 
-  rollup@4.38.0:
+  rollup@4.39.0:
     dependencies:
       '@types/estree': 1.0.7
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.38.0
-      '@rollup/rollup-android-arm64': 4.38.0
-      '@rollup/rollup-darwin-arm64': 4.38.0
-      '@rollup/rollup-darwin-x64': 4.38.0
-      '@rollup/rollup-freebsd-arm64': 4.38.0
-      '@rollup/rollup-freebsd-x64': 4.38.0
-      '@rollup/rollup-linux-arm-gnueabihf': 4.38.0
-      '@rollup/rollup-linux-arm-musleabihf': 4.38.0
-      '@rollup/rollup-linux-arm64-gnu': 4.38.0
-      '@rollup/rollup-linux-arm64-musl': 4.38.0
-      '@rollup/rollup-linux-loongarch64-gnu': 4.38.0
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.38.0
-      '@rollup/rollup-linux-riscv64-gnu': 4.38.0
-      '@rollup/rollup-linux-riscv64-musl': 4.38.0
-      '@rollup/rollup-linux-s390x-gnu': 4.38.0
-      '@rollup/rollup-linux-x64-gnu': 4.38.0
-      '@rollup/rollup-linux-x64-musl': 4.38.0
-      '@rollup/rollup-win32-arm64-msvc': 4.38.0
-      '@rollup/rollup-win32-ia32-msvc': 4.38.0
-      '@rollup/rollup-win32-x64-msvc': 4.38.0
+      '@rollup/rollup-android-arm-eabi': 4.39.0
+      '@rollup/rollup-android-arm64': 4.39.0
+      '@rollup/rollup-darwin-arm64': 4.39.0
+      '@rollup/rollup-darwin-x64': 4.39.0
+      '@rollup/rollup-freebsd-arm64': 4.39.0
+      '@rollup/rollup-freebsd-x64': 4.39.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.39.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.39.0
+      '@rollup/rollup-linux-arm64-gnu': 4.39.0
+      '@rollup/rollup-linux-arm64-musl': 4.39.0
+      '@rollup/rollup-linux-loongarch64-gnu': 4.39.0
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.39.0
+      '@rollup/rollup-linux-riscv64-musl': 4.39.0
+      '@rollup/rollup-linux-s390x-gnu': 4.39.0
+      '@rollup/rollup-linux-x64-gnu': 4.39.0
+      '@rollup/rollup-linux-x64-musl': 4.39.0
+      '@rollup/rollup-win32-arm64-msvc': 4.39.0
+      '@rollup/rollup-win32-ia32-msvc': 4.39.0
+      '@rollup/rollup-win32-x64-msvc': 4.39.0
       fsevents: 2.3.3
 
   run-parallel@1.2.0:
@@ -7149,7 +7155,7 @@ snapshots:
       debug: 4.4.0
       es-module-lexer: 1.6.0
       pathe: 2.0.3
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
     transitivePeerDependencies:
       - '@types/node'
       - jiti
@@ -7164,12 +7170,12 @@ snapshots:
       - tsx
       - yaml
 
-  vite-plugin-compression@0.5.1(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)):
+  vite-plugin-compression@0.5.1(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)):
     dependencies:
       chalk: 4.1.2
       debug: 4.4.0
       fs-extra: 10.1.0
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
     transitivePeerDependencies:
       - supports-color
 
@@ -7177,22 +7183,22 @@ snapshots:
     dependencies:
       monaco-editor: 0.52.2
 
-  vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)):
+  vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)):
     dependencies:
       debug: 4.4.0
       globrex: 0.1.2
       tsconfck: 3.1.4(typescript@5.8.2)
     optionalDependencies:
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1):
+  vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1):
     dependencies:
       esbuild: 0.25.2
       postcss: 8.5.3
-      rollup: 4.38.0
+      rollup: 4.39.0
     optionalDependencies:
       '@types/node': 22.13.14
       fsevents: 2.3.3
@@ -7203,7 +7209,7 @@ snapshots:
   vitest@3.0.9(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1):
     dependencies:
       '@vitest/expect': 3.0.9
-      '@vitest/mocker': 3.0.9(vite@6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
+      '@vitest/mocker': 3.0.9(vite@6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1))
       '@vitest/pretty-format': 3.0.9
       '@vitest/runner': 3.0.9
       '@vitest/snapshot': 3.0.9
@@ -7219,7 +7225,7 @@ snapshots:
       tinyexec: 0.3.2
       tinypool: 1.0.2
       tinyrainbow: 2.0.0
-      vite: 6.2.4(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
+      vite: 6.2.5(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
       vite-node: 3.0.9(@types/node@22.13.14)(jiti@1.21.7)(less@4.2.2)(yaml@2.7.1)
       why-is-node-running: 2.3.0
     optionalDependencies:
diff --git a/ui/src/app.tsx b/ui/src/app.tsx
index 18ef7353..836ac64d 100644
--- a/ui/src/app.tsx
+++ b/ui/src/app.tsx
@@ -43,15 +43,6 @@ export const App = () => (
                   <Route path={paths.warehouse} element={<Project />} />
                   <Route path={paths.downloads} element={<Downloads />} />
                   <Route path={paths.user} element={<User />} />
-                  <Route path={paths.settings} element={<Settings />} />
-                  <Route
-                    path={paths.settingsAnalysisTemplates}
-                    element={<Settings section='verification' />}
-                  />
-                  <Route
-                    path={paths.settingsClusterPromotionTasks}
-                    element={<Settings section='cluster-promotion-tasks' />}
-                  />
                   <Route
                     path={paths.createStage}
                     element={<Project tab='pipelines' creatingStage={true} />}
@@ -65,6 +56,10 @@ export const App = () => (
                     <Route index element={<ProjectSettings />} />
                     <Route path='*' element={<ProjectSettings />} />
                   </Route>
+                  <Route path={paths.settings}>
+                    <Route index element={<Settings />} />
+                    <Route path='*' element={<Settings />} />
+                  </Route>
                 </Route>
                 <Route path={paths.analysisRunLogs} element={<AnalysisRunLogsPage />} />
               </Route>
diff --git a/ui/src/config/paths.ts b/ui/src/config/paths.ts
index 63524413..3e3262ce 100644
--- a/ui/src/config/paths.ts
+++ b/ui/src/config/paths.ts
@@ -10,9 +10,7 @@ export const paths = {
   createWarehouse: '/project/:name/create-warehouse',
   projectSettings: '/project/:name/settings',
   user: '/user',
-  settings: '/settings/',
-  settingsAnalysisTemplates: '/settings/analysis-templates',
-  settingsClusterPromotionTasks: '/settings/cluster-promotion-tasks',
+  settings: '/settings',
   analysisRunLogs: '/ext/project/:name/stage/:stageName/analysis-run/:analysisRunId/logs',
   promotionTasks: '/project/:name/promotion-tasks',
 
diff --git a/ui/src/extensions/extensions-context.ts b/ui/src/extensions/extensions-context.ts
new file mode 100644
index 00000000..c408ebef
--- /dev/null
+++ b/ui/src/extensions/extensions-context.ts
@@ -0,0 +1,17 @@
+import React from 'react';
+
+import { Extension } from './types';
+
+type ExtensionsContextType = {
+  extensions: Extension[];
+};
+
+export const ExtensionsContext = React.createContext<ExtensionsContextType | null>(null);
+
+export const useExtensionsContext = () => {
+  const ctx = React.useContext(ExtensionsContext);
+
+  return {
+    stageTabs: ctx?.extensions.filter((extension) => extension.type === 'stageTab') || []
+  };
+};
diff --git a/ui/src/extensions/types.ts b/ui/src/extensions/types.ts
new file mode 100644
index 00000000..3a5e9d1a
--- /dev/null
+++ b/ui/src/extensions/types.ts
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { Stage } from '@ui/gen/api/v1alpha1/generated_pb';
+
+export type ExtensionStageTabComponentProps = {
+  stage: Stage;
+};
+
+export type ExtensionStageTab = {
+  type: 'stageTab';
+  component: ({ stage }: ExtensionStageTabComponentProps) => React.ReactNode;
+  label: string;
+  icon?: React.ReactNode;
+};
+
+export type Extension = ExtensionStageTab;
diff --git a/ui/src/features/settings/analysis-templates/analysis-templates.tsx b/ui/src/features/settings/analysis-templates/analysis-templates.tsx
index 6f9cc5b2..d75a48e3 100644
--- a/ui/src/features/settings/analysis-templates/analysis-templates.tsx
+++ b/ui/src/features/settings/analysis-templates/analysis-templates.tsx
@@ -1,7 +1,7 @@
 import { useMutation, useQuery } from '@connectrpc/connect-query';
 import { faPencil, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, Table } from 'antd';
+import { Button, Card, Table } from 'antd';
 import { format } from 'date-fns';
 
 import { useConfirmModal } from '@ui/features/common/confirm-modal/use-confirm-modal';
@@ -34,83 +34,87 @@ export const ClusterAnalysisTemplatesList = () => {
   );
 
   return (
-    <Table<ClusterAnalysisTemplate>
-      dataSource={data?.clusterAnalysisTemplates}
-      pagination={{ hideOnSinglePage: true }}
-      rowKey={(i) => i.metadata?.name || ''}
-      loading={isLoading}
-      expandable={descriptionExpandable()}
-      className='w-full'
+    <Card
+      title='Cluster Analysis Templates'
+      type='inner'
+      className='min-h-full'
+      extra={
+        <Button icon={<FontAwesomeIcon icon={faPlus} />} onClick={() => showCreate()}>
+          Add Template
+        </Button>
+      }
     >
-      <Table.Column<ClusterAnalysisTemplate>
-        title='Creation Date'
-        width={200}
-        render={(_, template) => {
-          const date = timestampDate(template.metadata?.creationTimestamp);
-          return date ? format(date, 'MMM do yyyy HH:mm:ss') : '';
-        }}
-      />
-      <Table.Column<ClusterAnalysisTemplate> title='Name' dataIndex={['metadata', 'name']} />
-      <Table.Column<ClusterAnalysisTemplate>
-        width={260}
-        title={
-          <div className='text-right'>
-            <Button
-              type='primary'
-              className='ml-auto text-xs font-semibold'
-              icon={<FontAwesomeIcon icon={faPlus} />}
-              onClick={() => showCreate()}
-            >
-              ADD TEMPLATE
-            </Button>
-          </div>
-        }
-        render={(_, template) => (
-          <div className='flex gap-2 justify-end'>
-            <Button
-              icon={<FontAwesomeIcon icon={faPencil} />}
-              className='mr-2 ml-auto'
-              onClick={() => {
-                showEdit((p) => (
-                  <EditClusterAnalysisTemplateModal
-                    {...p}
-                    templateName={template.metadata?.name || ''}
-                  />
-                ));
-              }}
-            >
-              Edit
-            </Button>
-            <Button
-              icon={<FontAwesomeIcon icon={faTrash} />}
-              danger
-              loading={isDeleting}
-              onClick={() => {
-                confirm({
-                  title: (
-                    <div className='flex items-center'>
-                      <FontAwesomeIcon icon={faTrash} className='mr-2' />
-                      Delete Cluster Analysis Template
-                    </div>
-                  ),
-                  content: (
-                    <p>
-                      Are you sure you want to delete ClusterAnalysisTemplate{' '}
-                      <b>{template?.metadata?.name}</b>?
-                    </p>
-                  ),
-                  onOk: () => {
-                    deleteTemplate({ name: template?.metadata?.name || '' });
-                  },
-                  hide: () => {}
-                });
-              }}
-            >
-              Delete
-            </Button>
-          </div>
-        )}
-      />
-    </Table>
+      <Table<ClusterAnalysisTemplate>
+        dataSource={data?.clusterAnalysisTemplates}
+        pagination={{ hideOnSinglePage: true }}
+        rowKey={(i) => i.metadata?.name || ''}
+        loading={isLoading}
+        expandable={descriptionExpandable()}
+        className='w-full'
+      >
+        <Table.Column<ClusterAnalysisTemplate>
+          title='Creation Date'
+          width={200}
+          render={(_, template) => {
+            const date = timestampDate(template.metadata?.creationTimestamp);
+            return date ? format(date, 'MMM do yyyy HH:mm:ss') : '';
+          }}
+        />
+        <Table.Column<ClusterAnalysisTemplate> title='Name' dataIndex={['metadata', 'name']} />
+        <Table.Column<ClusterAnalysisTemplate>
+          width={260}
+          render={(_, template) => (
+            <div className='flex gap-2 justify-end'>
+              <Button
+                icon={<FontAwesomeIcon icon={faPencil} />}
+                className='mr-2 ml-auto'
+                onClick={() => {
+                  showEdit((p) => (
+                    <EditClusterAnalysisTemplateModal
+                      {...p}
+                      templateName={template.metadata?.name || ''}
+                    />
+                  ));
+                }}
+                size='small'
+                color='default'
+                variant='filled'
+              >
+                Edit
+              </Button>
+              <Button
+                icon={<FontAwesomeIcon icon={faTrash} />}
+                loading={isDeleting}
+                onClick={() => {
+                  confirm({
+                    title: (
+                      <div className='flex items-center'>
+                        <FontAwesomeIcon icon={faTrash} className='mr-2' />
+                        Delete Cluster Analysis Template
+                      </div>
+                    ),
+                    content: (
+                      <p>
+                        Are you sure you want to delete ClusterAnalysisTemplate{' '}
+                        <b>{template?.metadata?.name}</b>?
+                      </p>
+                    ),
+                    onOk: () => {
+                      deleteTemplate({ name: template?.metadata?.name || '' });
+                    },
+                    hide: () => {}
+                  });
+                }}
+                size='small'
+                color='danger'
+                variant='filled'
+              >
+                Delete
+              </Button>
+            </div>
+          )}
+        />
+      </Table>
+    </Card>
   );
 };
diff --git a/ui/src/features/settings/cluster-promotion-tasks/cluster-promotion-tasks.tsx b/ui/src/features/settings/cluster-promotion-tasks/cluster-promotion-tasks.tsx
index 8acc80da..8f130d2e 100644
--- a/ui/src/features/settings/cluster-promotion-tasks/cluster-promotion-tasks.tsx
+++ b/ui/src/features/settings/cluster-promotion-tasks/cluster-promotion-tasks.tsx
@@ -1,7 +1,7 @@
 import { useMutation, useQuery } from '@connectrpc/connect-query';
 import { faPencil, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button, Flex, Table } from 'antd';
+import { Button, Card, Flex, Table } from 'antd';
 import { format } from 'date-fns';
 
 import { useConfirmModal } from '@ui/features/common/confirm-modal/use-confirm-modal';
@@ -61,7 +61,19 @@ export const ClusterPromotionTasks = () => {
     ));
 
   return (
-    <div className='p-4'>
+    <Card
+      title='Cluster Promotion Tasks'
+      type='inner'
+      className='min-h-full'
+      extra={
+        <Button
+          icon={<FontAwesomeIcon icon={faPlus} />}
+          onClick={() => onAddClusterPromotionTaskModalOpen()}
+        >
+          Add Cluster Promotion Task
+        </Button>
+      }
+    >
       <Table<ClusterPromotionTask>
         dataSource={listClusterPromotionTasksQuery.data?.clusterPromotionTasks}
         loading={listClusterPromotionTasksQuery.isFetching}
@@ -94,30 +106,23 @@ export const ClusterPromotionTasks = () => {
             render: (_, r) => r.metadata?.name
           },
           {
-            title: (
-              <div className='text-right'>
-                <Button
-                  type='primary'
-                  className='ml-auto text-xs font-semibold'
-                  icon={<FontAwesomeIcon icon={faPlus} />}
-                  onClick={onAddClusterPromotionTaskModalOpen}
-                >
-                  ADD CLUSTER PROMOTION TASK
-                </Button>
-              </div>
-            ),
             render: (_, template) => (
               <Flex gap={8} justify='end'>
                 <Button
                   icon={<FontAwesomeIcon icon={faPencil} />}
                   onClick={() => onEditClusterPromotionTaskModalOpen(template)}
+                  size='small'
+                  color='default'
+                  variant='filled'
                 >
                   Edit
                 </Button>
                 <Button
                   icon={<FontAwesomeIcon icon={faTrash} />}
-                  danger
                   onClick={() => onDeleteClusterPromotionTaskModalOpen(template)}
+                  size='small'
+                  color='danger'
+                  variant='filled'
                 >
                   Delete
                 </Button>
@@ -126,6 +131,6 @@ export const ClusterPromotionTasks = () => {
           }
         ]}
       />
-    </div>
+    </Card>
   );
 };
diff --git a/ui/src/features/settings/cluster-promotion-tasks/create-cluster-promotion-task.tsx b/ui/src/features/settings/cluster-promotion-tasks/create-cluster-promotion-task.tsx
index 10d70a69..71570c4d 100644
--- a/ui/src/features/settings/cluster-promotion-tasks/create-cluster-promotion-task.tsx
+++ b/ui/src/features/settings/cluster-promotion-tasks/create-cluster-promotion-task.tsx
@@ -1,5 +1,6 @@
 import { createConnectQueryKey, useMutation } from '@connectrpc/connect-query';
 import { useQueryClient } from '@tanstack/react-query';
+import { Button, Flex, Space } from 'antd';
 import Modal from 'antd/es/modal/Modal';
 import Link from 'antd/es/typography/Link';
 import { useForm } from 'react-hook-form';
@@ -52,21 +53,24 @@ export const CreateClusterPromotionTaskModal = (props: CreateClusterPromotionTas
     <Modal
       open={props.visible}
       onCancel={props.hide}
-      title={
-        <>
-          Create Cluster Promotion Task{' '}
+      title='Create Cluster Promotion Task'
+      width={700}
+      footer={
+        <Flex justify='space-between' align='center'>
           <Link
             href='https://docs.kargo.io/user-guide/reference-docs/promotion-tasks/#defining-a-global-promotion-task'
             target='_blank'
-            className='ml-1 text-xs'
           >
-            documentation
+            Documentation
           </Link>
-        </>
+          <Space>
+            <Button onClick={props.hide}>Cancel</Button>
+            <Button onClick={onSubmit} loading={createResourceMutation.isPending} type='primary'>
+              Create
+            </Button>
+          </Space>
+        </Flex>
       }
-      okText='Create'
-      width={700}
-      onOk={onSubmit}
     >
       <FieldContainer control={clusterPromotionTaskForm.control} name='value'>
         {({ field }) => (
diff --git a/ui/src/features/stage/stage-details.tsx b/ui/src/features/stage/stage-details.tsx
index 893fbea5..f59e60d7 100644
--- a/ui/src/features/stage/stage-details.tsx
+++ b/ui/src/features/stage/stage-details.tsx
@@ -13,6 +13,7 @@ import { useEffect, useMemo, useState } from 'react';
 import { generatePath, useNavigate, useParams } from 'react-router-dom';
 
 import { paths } from '@ui/config/paths';
+import { useExtensionsContext } from '@ui/extensions/extensions-context';
 import { Description } from '@ui/features/common/description';
 import { HealthStatusIcon } from '@ui/features/common/health-status/health-status-icon';
 import { StagePhaseIcon } from '@ui/features/common/stage-phase/stage-phase-icon';
@@ -93,6 +94,8 @@ export const StageDetails = ({ stage }: { stage: Stage }) => {
   const shardKey = stage?.metadata?.labels['kargo.akuity.io/shard'] || '';
   const argocdShard = config?.argocdShards?.[shardKey];
 
+  const { stageTabs } = useExtensionsContext();
+
   return (
     <Drawer
       open={!!stageName}
@@ -180,6 +183,12 @@ export const StageDetails = ({ stage }: { stage: Stage }) => {
                     />
                   )
                 },
+                ...stageTabs.map((data, index) => ({
+                  children: <data.component stage={stage} />,
+                  key: String(data.label + index),
+                  label: data.label,
+                  icon: data.icon
+                })),
                 {
                   key: TabsTypes.SETTINGS,
                   label: 'Settings',
diff --git a/ui/src/gen/api/v1alpha1/generated_pb.ts b/ui/src/gen/api/v1alpha1/generated_pb.ts
index 5a86e428..adebccb7 100644
--- a/ui/src/gen/api/v1alpha1/generated_pb.ts
+++ b/ui/src/gen/api/v1alpha1/generated_pb.ts
@@ -18,7 +18,7 @@ import type { Message } from "@bufbuild/protobuf";
  * Describes the file api/v1alpha1/generated.proto.
  */
 export const file_api_v1alpha1_generated: GenFile = /*@__PURE__*/
-  fileDesc("ChxhcGkvdjFhbHBoYTEvZ2VuZXJhdGVkLnByb3RvEiRnaXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEiMgoTQW5hbHlzaXNSdW5Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIrACChNBbmFseXNpc1J1bk1ldGFkYXRhElUKBmxhYmVscxgBIAMoCzJFLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5BbmFseXNpc1J1bk1ldGFkYXRhLkxhYmVsc0VudHJ5El8KC2Fubm90YXRpb25zGAIgAygLMkouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkFuYWx5c2lzUnVuTWV0YWRhdGEuQW5ub3RhdGlvbnNFbnRyeRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjIKEEFubm90YXRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJGChRBbmFseXNpc1J1blJlZmVyZW5jZRIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRINCgVwaGFzZRgDIAEoCSI3ChlBbmFseXNpc1RlbXBsYXRlUmVmZXJlbmNlEgwKBG5hbWUYASABKAkSDAoEa2luZBgCIAEoCSJPCg1BcHByb3ZlZFN0YWdlEj4KCmFwcHJvdmVkQXQYASABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZSI4ChVBcmdvQ0RBcHBIZWFsdGhTdGF0dXMSDgoGc3RhdHVzGAEgASgJEg8KB21lc3NhZ2UYAiABKAki1AEKD0FyZ29DREFwcFN0YXR1cxIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRJRCgxoZWFsdGhTdGF0dXMYAyABKAsyOy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXJnb0NEQXBwSGVhbHRoU3RhdHVzEk0KCnN5bmNTdGF0dXMYBCABKAsyOS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXJnb0NEQXBwU3luY1N0YXR1cyJKChNBcmdvQ0RBcHBTeW5jU3RhdHVzEg4KBnN0YXR1cxgBIAEoCRIQCghyZXZpc2lvbhgCIAEoCRIRCglyZXZpc2lvbnMYAyADKAkiNwoFQ2hhcnQSDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEg8KB3ZlcnNpb24YAyABKAkiYQoUQ2hhcnREaXNjb3ZlcnlSZXN1bHQSDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKEHNlbXZlckNvbnN0cmFpbnQYAyABKAkSEAoIdmVyc2lvbnMYBCADKAkiZAoRQ2hhcnRTdWJzY3JpcHRpb24SDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKEHNlbXZlckNvbnN0cmFpbnQYAyABKAkSFgoOZGlzY292ZXJ5TGltaXQYBCABKAUioQEKFENsdXN0ZXJQcm9tb3Rpb25UYXNrEkIKCG1ldGFkYXRhGAEgASgLMjAuazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLk9iamVjdE1ldGESRQoEc3BlYxgCIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25UYXNrU3BlYyKnAQoYQ2x1c3RlclByb21vdGlvblRhc2tMaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEkkKBWl0ZW1zGAIgAygLMjouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkNsdXN0ZXJQcm9tb3Rpb25UYXNrIkkKDEN1cnJlbnRTdGFnZRI5CgVzaW5jZRgBIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lIrYCChNEaXNjb3ZlcmVkQXJ0aWZhY3RzEkAKDGRpc2NvdmVyZWRBdBgEIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lEkUKA2dpdBgBIAMoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5HaXREaXNjb3ZlcnlSZXN1bHQSSgoGaW1hZ2VzGAIgAygLMjouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkltYWdlRGlzY292ZXJ5UmVzdWx0EkoKBmNoYXJ0cxgDIAMoCzI6LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DaGFydERpc2NvdmVyeVJlc3VsdCKwAQoQRGlzY292ZXJlZENvbW1pdBIKCgJpZBgBIAEoCRIOCgZicmFuY2gYAiABKAkSCwoDdGFnGAMgASgJEg8KB3N1YmplY3QYBCABKAkSDgoGYXV0aG9yGAUgASgJEhEKCWNvbW1pdHRlchgGIAEoCRI/CgtjcmVhdG9yRGF0ZRgHIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lIqQCChhEaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2USCwoDdGFnGAEgASgJEg4KBmRpZ2VzdBgCIAEoCRJkCgthbm5vdGF0aW9ucxgFIAMoCzJPLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2UuQW5ub3RhdGlvbnNFbnRyeRISCgpnaXRSZXBvVVJMGAMgASgJEj0KCWNyZWF0ZWRBdBgEIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lGjIKEEFubm90YXRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKiAwoHRnJlaWdodBJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEg0KBWFsaWFzGAcgASgJEkMKBm9yaWdpbhgJIAEoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0T3JpZ2luEkAKB2NvbW1pdHMYAyADKAsyLy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuR2l0Q29tbWl0EjsKBmltYWdlcxgEIAMoCzIrLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5JbWFnZRI7CgZjaGFydHMYBSADKAsyKy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQ2hhcnQSQwoGc3RhdHVzGAYgASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMirQIKEUZyZWlnaHRDb2xsZWN0aW9uEgoKAmlkGAMgASgJElEKBWl0ZW1zGAEgAygLMkIuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRDb2xsZWN0aW9uLkl0ZW1zRW50cnkSUwoTdmVyaWZpY2F0aW9uSGlzdG9yeRgCIAMoCzI2LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5WZXJpZmljYXRpb25JbmZvGmQKCkl0ZW1zRW50cnkSCwoDa2V5GAEgASgJEkUKBXZhbHVlGAIgASgLMjYuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRSZWZlcmVuY2U6AjgBIo0BCgtGcmVpZ2h0TGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRI8CgVpdGVtcxgCIAMoCzItLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0IisKDUZyZWlnaHRPcmlnaW4SDAoEa2luZBgBIAEoCRIMCgRuYW1lGAIgASgJIqECChBGcmVpZ2h0UmVmZXJlbmNlEgwKBG5hbWUYASABKAkSQwoGb3JpZ2luGAggASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRPcmlnaW4SQAoHY29tbWl0cxgCIAMoCzIvLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5HaXRDb21taXQSOwoGaW1hZ2VzGAMgAygLMisuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkltYWdlEjsKBmNoYXJ0cxgEIAMoCzIrLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DaGFydCKcAQoORnJlaWdodFJlcXVlc3QSQwoGb3JpZ2luGAEgASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRPcmlnaW4SRQoHc291cmNlcxgCIAEoCzI0LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0U291cmNlcyKYAQoORnJlaWdodFNvdXJjZXMSDgoGZGlyZWN0GAEgASgIEg4KBnN0YWdlcxgCIAMoCRJIChByZXF1aXJlZFNvYWtUaW1lGAMgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkR1cmF0aW9uEhwKFGF2YWlsYWJpbGl0eVN0cmF0ZWd5GAQgASgJItcECg1GcmVpZ2h0U3RhdHVzElkKC2N1cnJlbnRseUluGAMgAygLMkQuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuQ3VycmVudGx5SW5FbnRyeRJXCgp2ZXJpZmllZEluGAEgAygLMkMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuVmVyaWZpZWRJbkVudHJ5ElkKC2FwcHJvdmVkRm9yGAIgAygLMkQuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuQXBwcm92ZWRGb3JFbnRyeRpmChBDdXJyZW50bHlJbkVudHJ5EgsKA2tleRgBIAEoCRJBCgV2YWx1ZRgCIAEoCzIyLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DdXJyZW50U3RhZ2U6AjgBGmYKD1ZlcmlmaWVkSW5FbnRyeRILCgNrZXkYASABKAkSQgoFdmFsdWUYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuVmVyaWZpZWRTdGFnZToCOAEaZwoQQXBwcm92ZWRGb3JFbnRyeRILCgNrZXkYASABKAkSQgoFdmFsdWUYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXBwcm92ZWRTdGFnZToCOAEieQoJR2l0Q29tbWl0Eg8KB3JlcG9VUkwYASABKAkSCgoCaWQYAiABKAkSDgoGYnJhbmNoGAMgASgJEgsKA3RhZxgEIAEoCRIPCgdtZXNzYWdlGAYgASgJEg4KBmF1dGhvchgHIAEoCRIRCgljb21taXR0ZXIYCCABKAkibgoSR2l0RGlzY292ZXJ5UmVzdWx0Eg8KB3JlcG9VUkwYASABKAkSRwoHY29tbWl0cxgCIAMoCzI2LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkQ29tbWl0Io4CCg9HaXRTdWJzY3JpcHRpb24SDwoHcmVwb1VSTBgBIAEoCRIfChdjb21taXRTZWxlY3Rpb25TdHJhdGVneRgCIAEoCRIOCgZicmFuY2gYAyABKAkSFQoNc3RyaWN0U2VtdmVycxgLIAEoCBIYChBzZW12ZXJDb25zdHJhaW50GAQgASgJEhEKCWFsbG93VGFncxgFIAEoCRISCgppZ25vcmVUYWdzGAYgAygJEh0KFWluc2VjdXJlU2tpcFRMU1ZlcmlmeRgHIAEoCBIUCgxpbmNsdWRlUGF0aHMYCCADKAkSFAoMZXhjbHVkZVBhdGhzGAkgAygJEhYKDmRpc2NvdmVyeUxpbWl0GAogASgFIsgBCgZIZWFsdGgSDgoGc3RhdHVzGAEgASgJEg4KBmlzc3VlcxgCIAMoCRJOCgZjb25maWcYBCABKAsyPi5rOHMuaW8uYXBpZXh0ZW5zaW9uc19hcGlzZXJ2ZXIucGtnLmFwaXMuYXBpZXh0ZW5zaW9ucy52MS5KU09OEk4KBm91dHB1dBgFIAEoCzI+Lms4cy5pby5hcGlleHRlbnNpb25zX2FwaXNlcnZlci5wa2cuYXBpcy5hcGlleHRlbnNpb25zLnYxLkpTT04ibwoPSGVhbHRoQ2hlY2tTdGVwEgwKBHVzZXMYASABKAkSTgoGY29uZmlnGAIgASgLMj4uazhzLmlvLmFwaWV4dGVuc2lvbnNfYXBpc2VydmVyLnBrZy5hcGlzLmFwaWV4dGVuc2lvbnMudjEuSlNPTiLQAQoFSW1hZ2USDwoHcmVwb1VSTBgBIAEoCRISCgpnaXRSZXBvVVJMGAIgASgJEgsKA3RhZxgDIAEoCRIOCgZkaWdlc3QYBCABKAkSUQoLYW5ub3RhdGlvbnMYBSADKAsyPC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuSW1hZ2UuQW5ub3RhdGlvbnNFbnRyeRoyChBBbm5vdGF0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEijQEKFEltYWdlRGlzY292ZXJ5UmVzdWx0Eg8KB3JlcG9VUkwYASABKAkSEAoIcGxhdGZvcm0YAiABKAkSUgoKcmVmZXJlbmNlcxgDIAMoCzI+LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2Ui+QEKEUltYWdlU3Vic2NyaXB0aW9uEg8KB3JlcG9VUkwYASABKAkSEgoKZ2l0UmVwb1VSTBgCIAEoCRIeChZpbWFnZVNlbGVjdGlvblN0cmF0ZWd5GAMgASgJEhUKDXN0cmljdFNlbXZlcnMYCiABKAgSGAoQc2VtdmVyQ29uc3RyYWludBgEIAEoCRIRCglhbGxvd1RhZ3MYBSABKAkSEgoKaWdub3JlVGFncxgGIAMoCRIQCghwbGF0Zm9ybRgHIAEoCRIdChVpbnNlY3VyZVNraXBUTFNWZXJpZnkYCCABKAgSFgoOZGlzY292ZXJ5TGltaXQYCSABKAUi0wEKB1Byb2plY3QSQgoIbWV0YWRhdGEYASABKAsyMC5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuT2JqZWN0TWV0YRI/CgRzcGVjGAIgASgLMjEuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb2plY3RTcGVjEkMKBnN0YXR1cxgDIAEoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9qZWN0U3RhdHVzIo0BCgtQcm9qZWN0TGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRI8CgVpdGVtcxgCIAMoCzItLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9qZWN0Il8KC1Byb2plY3RTcGVjElAKEXByb21vdGlvblBvbGljaWVzGAEgAygLMjUuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblBvbGljeSJ0Cg1Qcm9qZWN0U3RhdHVzEkMKCmNvbmRpdGlvbnMYAyADKAsyLy5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuQ29uZGl0aW9uEg0KBXBoYXNlGAEgASgJEg8KB21lc3NhZ2UYAiABKAki2QEKCVByb21vdGlvbhJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEkEKBHNwZWMYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3BlYxJFCgZzdGF0dXMYAyABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RhdHVzIpEBCg1Qcm9tb3Rpb25MaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEj4KBWl0ZW1zGAIgAygLMi8uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvbiI+Cg9Qcm9tb3Rpb25Qb2xpY3kSDQoFc3RhZ2UYASABKAkSHAoUYXV0b1Byb21vdGlvbkVuYWJsZWQYAiABKAgi8gEKElByb21vdGlvblJlZmVyZW5jZRIMCgRuYW1lGAEgASgJEkcKB2ZyZWlnaHQYAiABKAsyNi5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodFJlZmVyZW5jZRJFCgZzdGF0dXMYAyABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RhdHVzEj4KCmZpbmlzaGVkQXQYBCABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZSK6AQoNUHJvbW90aW9uU3BlYxINCgVzdGFnZRgBIAEoCRIPCgdmcmVpZ2h0GAIgASgJEkUKBHZhcnMYBCADKAsyNy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVmFyaWFibGUSQgoFc3RlcHMYAyADKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RlcCK3BAoPUHJvbW90aW9uU3RhdHVzEhoKEmxhc3RIYW5kbGVkUmVmcmVzaBgEIAEoCRINCgVwaGFzZRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJEkcKB2ZyZWlnaHQYBSABKAsyNi5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodFJlZmVyZW5jZRJSChFmcmVpZ2h0Q29sbGVjdGlvbhgHIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0Q29sbGVjdGlvbhJLCgxoZWFsdGhDaGVja3MYCCADKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuSGVhbHRoQ2hlY2tTdGVwEj4KCmZpbmlzaGVkQXQYBiABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZRITCgtjdXJyZW50U3RlcBgJIAEoAxJaChVzdGVwRXhlY3V0aW9uTWV0YWRhdGEYCyADKAsyOy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuU3RlcEV4ZWN1dGlvbk1ldGFkYXRhEk0KBXN0YXRlGAogASgLMj4uazhzLmlvLmFwaWV4dGVuc2lvbnNfYXBpc2VydmVyLnBrZy5hcGlzLmFwaWV4dGVuc2lvbnMudjEuSlNPTiLhAgoNUHJvbW90aW9uU3RlcBIMCgR1c2VzGAEgASgJEkoKBHRhc2sYBSABKAsyPC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVGFza1JlZmVyZW5jZRIKCgJhcxgCIAEoCRIKCgJpZhgHIAEoCRJHCgVyZXRyeRgEIAEoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25TdGVwUmV0cnkSRQoEdmFycxgGIAMoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25WYXJpYWJsZRJOCgZjb25maWcYAyABKAsyPi5rOHMuaW8uYXBpZXh0ZW5zaW9uc19hcGlzZXJ2ZXIucGtnLmFwaXMuYXBpZXh0ZW5zaW9ucy52MS5KU09OIm0KElByb21vdGlvblN0ZXBSZXRyeRI/Cgd0aW1lb3V0GAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkR1cmF0aW9uEhYKDmVycm9yVGhyZXNob2xkGAIgASgNIpoBCg1Qcm9tb3Rpb25UYXNrEkIKCG1ldGFkYXRhGAEgASgLMjAuazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLk9iamVjdE1ldGESRQoEc3BlYxgCIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25UYXNrU3BlYyKZAQoRUHJvbW90aW9uVGFza0xpc3QSQAoIbWV0YWRhdGEYASABKAsyLi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuTGlzdE1ldGESQgoFaXRlbXMYAiADKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVGFzayI0ChZQcm9tb3Rpb25UYXNrUmVmZXJlbmNlEgwKBG5hbWUYASABKAkSDAoEa2luZBgCIAEoCSKeAQoRUHJvbW90aW9uVGFza1NwZWMSRQoEdmFycxgBIAMoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25WYXJpYWJsZRJCCgVzdGVwcxgCIAMoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25TdGVwIl4KEVByb21vdGlvblRlbXBsYXRlEkkKBHNwZWMYASABKAsyOy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVGVtcGxhdGVTcGVjIqIBChVQcm9tb3Rpb25UZW1wbGF0ZVNwZWMSRQoEdmFycxgCIAMoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25WYXJpYWJsZRJCCgVzdGVwcxgBIAMoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25TdGVwIjAKEVByb21vdGlvblZhcmlhYmxlEgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAki5gEKEFJlcG9TdWJzY3JpcHRpb24SQgoDZ2l0GAEgASgLMjUuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkdpdFN1YnNjcmlwdGlvbhJGCgVpbWFnZRgCIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5JbWFnZVN1YnNjcmlwdGlvbhJGCgVjaGFydBgDIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DaGFydFN1YnNjcmlwdGlvbiLNAQoFU3RhZ2USQgoIbWV0YWRhdGEYASABKAsyMC5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuT2JqZWN0TWV0YRI9CgRzcGVjGAIgASgLMi8uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlN0YWdlU3BlYxJBCgZzdGF0dXMYAyABKAsyMS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuU3RhZ2VTdGF0dXMiiQEKCVN0YWdlTGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRI6CgVpdGVtcxgCIAMoCzIrLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5TdGFnZSKIAgoJU3RhZ2VTcGVjEg0KBXNoYXJkGAQgASgJEk4KEHJlcXVlc3RlZEZyZWlnaHQYBSADKAsyNC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodFJlcXVlc3QSUgoRcHJvbW90aW9uVGVtcGxhdGUYBiABKAsyNy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVGVtcGxhdGUSSAoMdmVyaWZpY2F0aW9uGAMgASgLMjIuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlZlcmlmaWNhdGlvbiL2AwoLU3RhZ2VTdGF0dXMSQwoKY29uZGl0aW9ucxgNIAMoCzIvLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5Db25kaXRpb24SGgoSbGFzdEhhbmRsZWRSZWZyZXNoGAsgASgJEg0KBXBoYXNlGAEgASgJEk8KDmZyZWlnaHRIaXN0b3J5GAQgAygLMjcuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRDb2xsZWN0aW9uEhYKDmZyZWlnaHRTdW1tYXJ5GAwgASgJEjwKBmhlYWx0aBgIIAEoCzIsLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5IZWFsdGgSDwoHbWVzc2FnZRgJIAEoCRIaChJvYnNlcnZlZEdlbmVyYXRpb24YBiABKAMSUgoQY3VycmVudFByb21vdGlvbhgHIAEoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25SZWZlcmVuY2USTwoNbGFzdFByb21vdGlvbhgKIAEoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25SZWZlcmVuY2Ui2gEKFVN0ZXBFeGVjdXRpb25NZXRhZGF0YRINCgVhbGlhcxgBIAEoCRI9CglzdGFydGVkQXQYAiABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZRI+CgpmaW5pc2hlZEF0GAMgASgLMiouazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLlRpbWUSEgoKZXJyb3JDb3VudBgEIAEoDRIOCgZzdGF0dXMYBSABKAkSDwoHbWVzc2FnZRgGIAEoCSKLAgoMVmVyaWZpY2F0aW9uEloKEWFuYWx5c2lzVGVtcGxhdGVzGAEgAygLMj8uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkFuYWx5c2lzVGVtcGxhdGVSZWZlcmVuY2USVgoTYW5hbHlzaXNSdW5NZXRhZGF0YRgCIAEoCzI5LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5BbmFseXNpc1J1bk1ldGFkYXRhEkcKBGFyZ3MYAyADKAsyOS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQW5hbHlzaXNSdW5Bcmd1bWVudCKdAgoQVmVyaWZpY2F0aW9uSW5mbxIKCgJpZBgEIAEoCRINCgVhY3RvchgHIAEoCRI9CglzdGFydFRpbWUYBSABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZRINCgVwaGFzZRgBIAEoCRIPCgdtZXNzYWdlGAIgASgJEk8KC2FuYWx5c2lzUnVuGAMgASgLMjouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkFuYWx5c2lzUnVuUmVmZXJlbmNlEj4KCmZpbmlzaFRpbWUYBiABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZSKUAQoNVmVyaWZpZWRTdGFnZRI+Cgp2ZXJpZmllZEF0GAEgASgLMiouazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLlRpbWUSQwoLbG9uZ2VzdFNvYWsYAiABKAsyLi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuRHVyYXRpb24i2QEKCVdhcmVob3VzZRJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEkEKBHNwZWMYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuV2FyZWhvdXNlU3BlYxJFCgZzdGF0dXMYAyABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuV2FyZWhvdXNlU3RhdHVzIpEBCg1XYXJlaG91c2VMaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEj4KBWl0ZW1zGAIgAygLMi8uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLldhcmVob3VzZSLOAQoNV2FyZWhvdXNlU3BlYxINCgVzaGFyZBgCIAEoCRJACghpbnRlcnZhbBgEIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5EdXJhdGlvbhIdChVmcmVpZ2h0Q3JlYXRpb25Qb2xpY3kYAyABKAkSTQoNc3Vic2NyaXB0aW9ucxgBIAMoCzI2LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5SZXBvU3Vic2NyaXB0aW9uIv0BCg9XYXJlaG91c2VTdGF0dXMSQwoKY29uZGl0aW9ucxgJIAMoCzIvLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5Db25kaXRpb24SGgoSbGFzdEhhbmRsZWRSZWZyZXNoGAYgASgJEhoKEm9ic2VydmVkR2VuZXJhdGlvbhgEIAEoAxIVCg1sYXN0RnJlaWdodElEGAggASgJElYKE2Rpc2NvdmVyZWRBcnRpZmFjdHMYByABKAsyOS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRGlzY292ZXJlZEFydGlmYWN0c0KXAgooY29tLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMUIOR2VuZXJhdGVkUHJvdG9QAVokZ2l0aHViLmNvbS9ha3VpdHkva2FyZ28vYXBpL3YxYWxwaGExogIFR0NBS0GqAiRHaXRodWIuQ29tLkFrdWl0eS5LYXJnby5BcGkuVjFhbHBoYTHKAiRHaXRodWJcQ29tXEFrdWl0eVxLYXJnb1xBcGlcVjFhbHBoYTHiAjBHaXRodWJcQ29tXEFrdWl0eVxLYXJnb1xBcGlcVjFhbHBoYTFcR1BCTWV0YWRhdGHqAilHaXRodWI6OkNvbTo6QWt1aXR5OjpLYXJnbzo6QXBpOjpWMWFscGhhMQ", [file_k8s_io_apiextensions_apiserver_pkg_apis_apiextensions_v1_generated, file_k8s_io_apimachinery_pkg_apis_meta_v1_generated, file_k8s_io_apimachinery_pkg_runtime_generated, file_k8s_io_apimachinery_pkg_runtime_schema_generated]);
+  fileDesc("ChxhcGkvdjFhbHBoYTEvZ2VuZXJhdGVkLnByb3RvEiRnaXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEiMgoTQW5hbHlzaXNSdW5Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIrACChNBbmFseXNpc1J1bk1ldGFkYXRhElUKBmxhYmVscxgBIAMoCzJFLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5BbmFseXNpc1J1bk1ldGFkYXRhLkxhYmVsc0VudHJ5El8KC2Fubm90YXRpb25zGAIgAygLMkouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkFuYWx5c2lzUnVuTWV0YWRhdGEuQW5ub3RhdGlvbnNFbnRyeRotCgtMYWJlbHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBGjIKEEFubm90YXRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJGChRBbmFseXNpc1J1blJlZmVyZW5jZRIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRINCgVwaGFzZRgDIAEoCSI3ChlBbmFseXNpc1RlbXBsYXRlUmVmZXJlbmNlEgwKBG5hbWUYASABKAkSDAoEa2luZBgCIAEoCSJPCg1BcHByb3ZlZFN0YWdlEj4KCmFwcHJvdmVkQXQYASABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZSI4ChVBcmdvQ0RBcHBIZWFsdGhTdGF0dXMSDgoGc3RhdHVzGAEgASgJEg8KB21lc3NhZ2UYAiABKAki1AEKD0FyZ29DREFwcFN0YXR1cxIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRJRCgxoZWFsdGhTdGF0dXMYAyABKAsyOy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXJnb0NEQXBwSGVhbHRoU3RhdHVzEk0KCnN5bmNTdGF0dXMYBCABKAsyOS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXJnb0NEQXBwU3luY1N0YXR1cyJKChNBcmdvQ0RBcHBTeW5jU3RhdHVzEg4KBnN0YXR1cxgBIAEoCRIQCghyZXZpc2lvbhgCIAEoCRIRCglyZXZpc2lvbnMYAyADKAkiNwoFQ2hhcnQSDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEg8KB3ZlcnNpb24YAyABKAkiYQoUQ2hhcnREaXNjb3ZlcnlSZXN1bHQSDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKEHNlbXZlckNvbnN0cmFpbnQYAyABKAkSEAoIdmVyc2lvbnMYBCADKAkiZAoRQ2hhcnRTdWJzY3JpcHRpb24SDwoHcmVwb1VSTBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKEHNlbXZlckNvbnN0cmFpbnQYAyABKAkSFgoOZGlzY292ZXJ5TGltaXQYBCABKAUioQEKFENsdXN0ZXJQcm9tb3Rpb25UYXNrEkIKCG1ldGFkYXRhGAEgASgLMjAuazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLk9iamVjdE1ldGESRQoEc3BlYxgCIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25UYXNrU3BlYyKnAQoYQ2x1c3RlclByb21vdGlvblRhc2tMaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEkkKBWl0ZW1zGAIgAygLMjouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkNsdXN0ZXJQcm9tb3Rpb25UYXNrIkkKDEN1cnJlbnRTdGFnZRI5CgVzaW5jZRgBIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lIrYCChNEaXNjb3ZlcmVkQXJ0aWZhY3RzEkAKDGRpc2NvdmVyZWRBdBgEIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lEkUKA2dpdBgBIAMoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5HaXREaXNjb3ZlcnlSZXN1bHQSSgoGaW1hZ2VzGAIgAygLMjouZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkltYWdlRGlzY292ZXJ5UmVzdWx0EkoKBmNoYXJ0cxgDIAMoCzI6LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DaGFydERpc2NvdmVyeVJlc3VsdCKwAQoQRGlzY292ZXJlZENvbW1pdBIKCgJpZBgBIAEoCRIOCgZicmFuY2gYAiABKAkSCwoDdGFnGAMgASgJEg8KB3N1YmplY3QYBCABKAkSDgoGYXV0aG9yGAUgASgJEhEKCWNvbW1pdHRlchgGIAEoCRI/CgtjcmVhdG9yRGF0ZRgHIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lIqQCChhEaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2USCwoDdGFnGAEgASgJEg4KBmRpZ2VzdBgCIAEoCRJkCgthbm5vdGF0aW9ucxgFIAMoCzJPLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2UuQW5ub3RhdGlvbnNFbnRyeRISCgpnaXRSZXBvVVJMGAMgASgJEj0KCWNyZWF0ZWRBdBgEIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lGjIKEEFubm90YXRpb25zRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASIxChJFeHByZXNzaW9uVmFyaWFibGUSDAoEbmFtZRgBIAEoCRINCgV2YWx1ZRgCIAEoCSKiAwoHRnJlaWdodBJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEg0KBWFsaWFzGAcgASgJEkMKBm9yaWdpbhgJIAEoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0T3JpZ2luEkAKB2NvbW1pdHMYAyADKAsyLy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuR2l0Q29tbWl0EjsKBmltYWdlcxgEIAMoCzIrLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5JbWFnZRI7CgZjaGFydHMYBSADKAsyKy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQ2hhcnQSQwoGc3RhdHVzGAYgASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMirQIKEUZyZWlnaHRDb2xsZWN0aW9uEgoKAmlkGAMgASgJElEKBWl0ZW1zGAEgAygLMkIuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRDb2xsZWN0aW9uLkl0ZW1zRW50cnkSUwoTdmVyaWZpY2F0aW9uSGlzdG9yeRgCIAMoCzI2LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5WZXJpZmljYXRpb25JbmZvGmQKCkl0ZW1zRW50cnkSCwoDa2V5GAEgASgJEkUKBXZhbHVlGAIgASgLMjYuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRSZWZlcmVuY2U6AjgBIo0BCgtGcmVpZ2h0TGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRI8CgVpdGVtcxgCIAMoCzItLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0IisKDUZyZWlnaHRPcmlnaW4SDAoEa2luZBgBIAEoCRIMCgRuYW1lGAIgASgJIqECChBGcmVpZ2h0UmVmZXJlbmNlEgwKBG5hbWUYASABKAkSQwoGb3JpZ2luGAggASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRPcmlnaW4SQAoHY29tbWl0cxgCIAMoCzIvLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5HaXRDb21taXQSOwoGaW1hZ2VzGAMgAygLMisuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkltYWdlEjsKBmNoYXJ0cxgEIAMoCzIrLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DaGFydCKcAQoORnJlaWdodFJlcXVlc3QSQwoGb3JpZ2luGAEgASgLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRPcmlnaW4SRQoHc291cmNlcxgCIAEoCzI0LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0U291cmNlcyKYAQoORnJlaWdodFNvdXJjZXMSDgoGZGlyZWN0GAEgASgIEg4KBnN0YWdlcxgCIAMoCRJIChByZXF1aXJlZFNvYWtUaW1lGAMgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkR1cmF0aW9uEhwKFGF2YWlsYWJpbGl0eVN0cmF0ZWd5GAQgASgJItcECg1GcmVpZ2h0U3RhdHVzElkKC2N1cnJlbnRseUluGAMgAygLMkQuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuQ3VycmVudGx5SW5FbnRyeRJXCgp2ZXJpZmllZEluGAEgAygLMkMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuVmVyaWZpZWRJbkVudHJ5ElkKC2FwcHJvdmVkRm9yGAIgAygLMkQuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRTdGF0dXMuQXBwcm92ZWRGb3JFbnRyeRpmChBDdXJyZW50bHlJbkVudHJ5EgsKA2tleRgBIAEoCRJBCgV2YWx1ZRgCIAEoCzIyLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5DdXJyZW50U3RhZ2U6AjgBGmYKD1ZlcmlmaWVkSW5FbnRyeRILCgNrZXkYASABKAkSQgoFdmFsdWUYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuVmVyaWZpZWRTdGFnZToCOAEaZwoQQXBwcm92ZWRGb3JFbnRyeRILCgNrZXkYASABKAkSQgoFdmFsdWUYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQXBwcm92ZWRTdGFnZToCOAEieQoJR2l0Q29tbWl0Eg8KB3JlcG9VUkwYASABKAkSCgoCaWQYAiABKAkSDgoGYnJhbmNoGAMgASgJEgsKA3RhZxgEIAEoCRIPCgdtZXNzYWdlGAYgASgJEg4KBmF1dGhvchgHIAEoCRIRCgljb21taXR0ZXIYCCABKAkibgoSR2l0RGlzY292ZXJ5UmVzdWx0Eg8KB3JlcG9VUkwYASABKAkSRwoHY29tbWl0cxgCIAMoCzI2LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkQ29tbWl0Io4CCg9HaXRTdWJzY3JpcHRpb24SDwoHcmVwb1VSTBgBIAEoCRIfChdjb21taXRTZWxlY3Rpb25TdHJhdGVneRgCIAEoCRIOCgZicmFuY2gYAyABKAkSFQoNc3RyaWN0U2VtdmVycxgLIAEoCBIYChBzZW12ZXJDb25zdHJhaW50GAQgASgJEhEKCWFsbG93VGFncxgFIAEoCRISCgppZ25vcmVUYWdzGAYgAygJEh0KFWluc2VjdXJlU2tpcFRMU1ZlcmlmeRgHIAEoCBIUCgxpbmNsdWRlUGF0aHMYCCADKAkSFAoMZXhjbHVkZVBhdGhzGAkgAygJEhYKDmRpc2NvdmVyeUxpbWl0GAogASgFIsgBCgZIZWFsdGgSDgoGc3RhdHVzGAEgASgJEg4KBmlzc3VlcxgCIAMoCRJOCgZjb25maWcYBCABKAsyPi5rOHMuaW8uYXBpZXh0ZW5zaW9uc19hcGlzZXJ2ZXIucGtnLmFwaXMuYXBpZXh0ZW5zaW9ucy52MS5KU09OEk4KBm91dHB1dBgFIAEoCzI+Lms4cy5pby5hcGlleHRlbnNpb25zX2FwaXNlcnZlci5wa2cuYXBpcy5hcGlleHRlbnNpb25zLnYxLkpTT04ibwoPSGVhbHRoQ2hlY2tTdGVwEgwKBHVzZXMYASABKAkSTgoGY29uZmlnGAIgASgLMj4uazhzLmlvLmFwaWV4dGVuc2lvbnNfYXBpc2VydmVyLnBrZy5hcGlzLmFwaWV4dGVuc2lvbnMudjEuSlNPTiLQAQoFSW1hZ2USDwoHcmVwb1VSTBgBIAEoCRISCgpnaXRSZXBvVVJMGAIgASgJEgsKA3RhZxgDIAEoCRIOCgZkaWdlc3QYBCABKAkSUQoLYW5ub3RhdGlvbnMYBSADKAsyPC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuSW1hZ2UuQW5ub3RhdGlvbnNFbnRyeRoyChBBbm5vdGF0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEijQEKFEltYWdlRGlzY292ZXJ5UmVzdWx0Eg8KB3JlcG9VUkwYASABKAkSEAoIcGxhdGZvcm0YAiABKAkSUgoKcmVmZXJlbmNlcxgDIAMoCzI+LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkSW1hZ2VSZWZlcmVuY2Ui+QEKEUltYWdlU3Vic2NyaXB0aW9uEg8KB3JlcG9VUkwYASABKAkSEgoKZ2l0UmVwb1VSTBgCIAEoCRIeChZpbWFnZVNlbGVjdGlvblN0cmF0ZWd5GAMgASgJEhUKDXN0cmljdFNlbXZlcnMYCiABKAgSGAoQc2VtdmVyQ29uc3RyYWludBgEIAEoCRIRCglhbGxvd1RhZ3MYBSABKAkSEgoKaWdub3JlVGFncxgGIAMoCRIQCghwbGF0Zm9ybRgHIAEoCRIdChVpbnNlY3VyZVNraXBUTFNWZXJpZnkYCCABKAgSFgoOZGlzY292ZXJ5TGltaXQYCSABKAUi0wEKB1Byb2plY3QSQgoIbWV0YWRhdGEYASABKAsyMC5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuT2JqZWN0TWV0YRI/CgRzcGVjGAIgASgLMjEuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb2plY3RTcGVjEkMKBnN0YXR1cxgDIAEoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9qZWN0U3RhdHVzIo0BCgtQcm9qZWN0TGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRI8CgVpdGVtcxgCIAMoCzItLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9qZWN0Il8KC1Byb2plY3RTcGVjElAKEXByb21vdGlvblBvbGljaWVzGAEgAygLMjUuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblBvbGljeSJ0Cg1Qcm9qZWN0U3RhdHVzEkMKCmNvbmRpdGlvbnMYAyADKAsyLy5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuQ29uZGl0aW9uEg0KBXBoYXNlGAEgASgJEg8KB21lc3NhZ2UYAiABKAki2QEKCVByb21vdGlvbhJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEkEKBHNwZWMYAiABKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3BlYxJFCgZzdGF0dXMYAyABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RhdHVzIpEBCg1Qcm9tb3Rpb25MaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEj4KBWl0ZW1zGAIgAygLMi8uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvbiI+Cg9Qcm9tb3Rpb25Qb2xpY3kSDQoFc3RhZ2UYASABKAkSHAoUYXV0b1Byb21vdGlvbkVuYWJsZWQYAiABKAgi8gEKElByb21vdGlvblJlZmVyZW5jZRIMCgRuYW1lGAEgASgJEkcKB2ZyZWlnaHQYAiABKAsyNi5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodFJlZmVyZW5jZRJFCgZzdGF0dXMYAyABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RhdHVzEj4KCmZpbmlzaGVkQXQYBCABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZSK7AQoNUHJvbW90aW9uU3BlYxINCgVzdGFnZRgBIAEoCRIPCgdmcmVpZ2h0GAIgASgJEkYKBHZhcnMYBCADKAsyOC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRXhwcmVzc2lvblZhcmlhYmxlEkIKBXN0ZXBzGAMgAygLMjMuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblN0ZXAitwQKD1Byb21vdGlvblN0YXR1cxIaChJsYXN0SGFuZGxlZFJlZnJlc2gYBCABKAkSDQoFcGhhc2UYASABKAkSDwoHbWVzc2FnZRgCIAEoCRJHCgdmcmVpZ2h0GAUgASgLMjYuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkZyZWlnaHRSZWZlcmVuY2USUgoRZnJlaWdodENvbGxlY3Rpb24YByABKAsyNy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodENvbGxlY3Rpb24SSwoMaGVhbHRoQ2hlY2tzGAggAygLMjUuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkhlYWx0aENoZWNrU3RlcBI+CgpmaW5pc2hlZEF0GAYgASgLMiouazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLlRpbWUSEwoLY3VycmVudFN0ZXAYCSABKAMSWgoVc3RlcEV4ZWN1dGlvbk1ldGFkYXRhGAsgAygLMjsuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlN0ZXBFeGVjdXRpb25NZXRhZGF0YRJNCgVzdGF0ZRgKIAEoCzI+Lms4cy5pby5hcGlleHRlbnNpb25zX2FwaXNlcnZlci5wa2cuYXBpcy5hcGlleHRlbnNpb25zLnYxLkpTT04i4gIKDVByb21vdGlvblN0ZXASDAoEdXNlcxgBIAEoCRJKCgR0YXNrGAUgASgLMjwuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblRhc2tSZWZlcmVuY2USCgoCYXMYAiABKAkSCgoCaWYYByABKAkSRwoFcmV0cnkYBCABKAsyOC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RlcFJldHJ5EkYKBHZhcnMYBiADKAsyOC5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRXhwcmVzc2lvblZhcmlhYmxlEk4KBmNvbmZpZxgDIAEoCzI+Lms4cy5pby5hcGlleHRlbnNpb25zX2FwaXNlcnZlci5wa2cuYXBpcy5hcGlleHRlbnNpb25zLnYxLkpTT04ibQoSUHJvbW90aW9uU3RlcFJldHJ5Ej8KB3RpbWVvdXQYASABKAsyLi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuRHVyYXRpb24SFgoOZXJyb3JUaHJlc2hvbGQYAiABKA0imgEKDVByb21vdGlvblRhc2sSQgoIbWV0YWRhdGEYASABKAsyMC5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuT2JqZWN0TWV0YRJFCgRzcGVjGAIgASgLMjcuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblRhc2tTcGVjIpkBChFQcm9tb3Rpb25UYXNrTGlzdBJACghtZXRhZGF0YRgBIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5MaXN0TWV0YRJCCgVpdGVtcxgCIAMoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25UYXNrIjQKFlByb21vdGlvblRhc2tSZWZlcmVuY2USDAoEbmFtZRgBIAEoCRIMCgRraW5kGAIgASgJIp8BChFQcm9tb3Rpb25UYXNrU3BlYxJGCgR2YXJzGAEgAygLMjguZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkV4cHJlc3Npb25WYXJpYWJsZRJCCgVzdGVwcxgCIAMoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25TdGVwIl4KEVByb21vdGlvblRlbXBsYXRlEkkKBHNwZWMYASABKAsyOy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uVGVtcGxhdGVTcGVjIqMBChVQcm9tb3Rpb25UZW1wbGF0ZVNwZWMSRgoEdmFycxgCIAMoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5FeHByZXNzaW9uVmFyaWFibGUSQgoFc3RlcHMYASADKAsyMy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuUHJvbW90aW9uU3RlcCLmAQoQUmVwb1N1YnNjcmlwdGlvbhJCCgNnaXQYASABKAsyNS5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuR2l0U3Vic2NyaXB0aW9uEkYKBWltYWdlGAIgASgLMjcuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkltYWdlU3Vic2NyaXB0aW9uEkYKBWNoYXJ0GAMgASgLMjcuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkNoYXJ0U3Vic2NyaXB0aW9uIs0BCgVTdGFnZRJCCghtZXRhZGF0YRgBIAEoCzIwLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5PYmplY3RNZXRhEj0KBHNwZWMYAiABKAsyLy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuU3RhZ2VTcGVjEkEKBnN0YXR1cxgDIAEoCzIxLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5TdGFnZVN0YXR1cyKJAQoJU3RhZ2VMaXN0EkAKCG1ldGFkYXRhGAEgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkxpc3RNZXRhEjoKBWl0ZW1zGAIgAygLMisuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlN0YWdlItACCglTdGFnZVNwZWMSDQoFc2hhcmQYBCABKAkSRgoEdmFycxgHIAMoCzI4LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5FeHByZXNzaW9uVmFyaWFibGUSTgoQcmVxdWVzdGVkRnJlaWdodBgFIAMoCzI0LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5GcmVpZ2h0UmVxdWVzdBJSChFwcm9tb3Rpb25UZW1wbGF0ZRgGIAEoCzI3LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5Qcm9tb3Rpb25UZW1wbGF0ZRJICgx2ZXJpZmljYXRpb24YAyABKAsyMi5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuVmVyaWZpY2F0aW9uIvYDCgtTdGFnZVN0YXR1cxJDCgpjb25kaXRpb25zGA0gAygLMi8uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkNvbmRpdGlvbhIaChJsYXN0SGFuZGxlZFJlZnJlc2gYCyABKAkSDQoFcGhhc2UYASABKAkSTwoOZnJlaWdodEhpc3RvcnkYBCADKAsyNy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuRnJlaWdodENvbGxlY3Rpb24SFgoOZnJlaWdodFN1bW1hcnkYDCABKAkSPAoGaGVhbHRoGAggASgLMiwuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkhlYWx0aBIPCgdtZXNzYWdlGAkgASgJEhoKEm9ic2VydmVkR2VuZXJhdGlvbhgGIAEoAxJSChBjdXJyZW50UHJvbW90aW9uGAcgASgLMjguZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblJlZmVyZW5jZRJPCg1sYXN0UHJvbW90aW9uGAogASgLMjguZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlByb21vdGlvblJlZmVyZW5jZSLaAQoVU3RlcEV4ZWN1dGlvbk1ldGFkYXRhEg0KBWFsaWFzGAEgASgJEj0KCXN0YXJ0ZWRBdBgCIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lEj4KCmZpbmlzaGVkQXQYAyABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZRISCgplcnJvckNvdW50GAQgASgNEg4KBnN0YXR1cxgFIAEoCRIPCgdtZXNzYWdlGAYgASgJIosCCgxWZXJpZmljYXRpb24SWgoRYW5hbHlzaXNUZW1wbGF0ZXMYASADKAsyPy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQW5hbHlzaXNUZW1wbGF0ZVJlZmVyZW5jZRJWChNhbmFseXNpc1J1bk1ldGFkYXRhGAIgASgLMjkuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLkFuYWx5c2lzUnVuTWV0YWRhdGESRwoEYXJncxgDIAMoCzI5LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5BbmFseXNpc1J1bkFyZ3VtZW50Ip0CChBWZXJpZmljYXRpb25JbmZvEgoKAmlkGAQgASgJEg0KBWFjdG9yGAcgASgJEj0KCXN0YXJ0VGltZRgFIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lEg0KBXBoYXNlGAEgASgJEg8KB21lc3NhZ2UYAiABKAkSTwoLYW5hbHlzaXNSdW4YAyABKAsyOi5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuQW5hbHlzaXNSdW5SZWZlcmVuY2USPgoKZmluaXNoVGltZRgGIAEoCzIqLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5UaW1lIpQBCg1WZXJpZmllZFN0YWdlEj4KCnZlcmlmaWVkQXQYASABKAsyKi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuVGltZRJDCgtsb25nZXN0U29haxgCIAEoCzIuLms4cy5pby5hcGltYWNoaW5lcnkucGtnLmFwaXMubWV0YS52MS5EdXJhdGlvbiLZAQoJV2FyZWhvdXNlEkIKCG1ldGFkYXRhGAEgASgLMjAuazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLk9iamVjdE1ldGESQQoEc3BlYxgCIAEoCzIzLmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5XYXJlaG91c2VTcGVjEkUKBnN0YXR1cxgDIAEoCzI1LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5XYXJlaG91c2VTdGF0dXMikQEKDVdhcmVob3VzZUxpc3QSQAoIbWV0YWRhdGEYASABKAsyLi5rOHMuaW8uYXBpbWFjaGluZXJ5LnBrZy5hcGlzLm1ldGEudjEuTGlzdE1ldGESPgoFaXRlbXMYAiADKAsyLy5naXRodWIuY29tLmFrdWl0eS5rYXJnby5hcGkudjFhbHBoYTEuV2FyZWhvdXNlIs4BCg1XYXJlaG91c2VTcGVjEg0KBXNoYXJkGAIgASgJEkAKCGludGVydmFsGAQgASgLMi4uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkR1cmF0aW9uEh0KFWZyZWlnaHRDcmVhdGlvblBvbGljeRgDIAEoCRJNCg1zdWJzY3JpcHRpb25zGAEgAygLMjYuZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExLlJlcG9TdWJzY3JpcHRpb24i/QEKD1dhcmVob3VzZVN0YXR1cxJDCgpjb25kaXRpb25zGAkgAygLMi8uazhzLmlvLmFwaW1hY2hpbmVyeS5wa2cuYXBpcy5tZXRhLnYxLkNvbmRpdGlvbhIaChJsYXN0SGFuZGxlZFJlZnJlc2gYBiABKAkSGgoSb2JzZXJ2ZWRHZW5lcmF0aW9uGAQgASgDEhUKDWxhc3RGcmVpZ2h0SUQYCCABKAkSVgoTZGlzY292ZXJlZEFydGlmYWN0cxgHIAEoCzI5LmdpdGh1Yi5jb20uYWt1aXR5LmthcmdvLmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkQXJ0aWZhY3RzQpcCCihjb20uZ2l0aHViLmNvbS5ha3VpdHkua2FyZ28uYXBpLnYxYWxwaGExQg5HZW5lcmF0ZWRQcm90b1ABWiRnaXRodWIuY29tL2FrdWl0eS9rYXJnby9hcGkvdjFhbHBoYTGiAgVHQ0FLQaoCJEdpdGh1Yi5Db20uQWt1aXR5LkthcmdvLkFwaS5WMWFscGhhMcoCJEdpdGh1YlxDb21cQWt1aXR5XEthcmdvXEFwaVxWMWFscGhhMeICMEdpdGh1YlxDb21cQWt1aXR5XEthcmdvXEFwaVxWMWFscGhhMVxHUEJNZXRhZGF0YeoCKUdpdGh1Yjo6Q29tOjpBa3VpdHk6OkthcmdvOjpBcGk6OlYxYWxwaGEx", [file_k8s_io_apiextensions_apiserver_pkg_apis_apiextensions_v1_generated, file_k8s_io_apimachinery_pkg_apis_meta_v1_generated, file_k8s_io_apimachinery_pkg_runtime_generated, file_k8s_io_apimachinery_pkg_runtime_schema_generated]);
 
 /**
  * AnalysisRunArgument represents an argument to be added to an AnalysisRun.
@@ -691,6 +691,44 @@ export type DiscoveredImageReference = Message<"github.com.akuity.kargo.api.v1al
 export const DiscoveredImageReferenceSchema: GenMessage<DiscoveredImageReference> = /*@__PURE__*/
   messageDesc(file_api_v1alpha1_generated, 16);
 
+/**
+ * ExpressionVariable describes a single variable that may be referenced by
+ * expressions in the context of a ClusterPromotionTask, PromotionTask,
+ * Promotion, AnalysisRun arguments, or other objects that support expressions.
+ *
+ * It is used to pass information to the expression evaluation engine, and to
+ * allow for dynamic evaluation of expressions based on the variable values.
+ *
+ * @generated from message github.com.akuity.kargo.api.v1alpha1.ExpressionVariable
+ */
+export type ExpressionVariable = Message<"github.com.akuity.kargo.api.v1alpha1.ExpressionVariable"> & {
+  /**
+   * Name is the name of the variable.
+   *
+   * +kubebuilder:validation:MinLength=1
+   * +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
+   *
+   * @generated from field: optional string name = 1;
+   */
+  name: string;
+
+  /**
+   * Value is the value of the variable. It is allowed to utilize expressions
+   * in the value.
+   * See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
+   *
+   * @generated from field: optional string value = 2;
+   */
+  value: string;
+};
+
+/**
+ * Describes the message github.com.akuity.kargo.api.v1alpha1.ExpressionVariable.
+ * Use `create(ExpressionVariableSchema)` to create a new message.
+ */
+export const ExpressionVariableSchema: GenMessage<ExpressionVariable> = /*@__PURE__*/
+  messageDesc(file_api_v1alpha1_generated, 17);
+
 /**
  * Freight represents a collection of versioned artifacts.
  *
@@ -760,7 +798,7 @@ export type Freight = Message<"github.com.akuity.kargo.api.v1alpha1.Freight"> &
  * Use `create(FreightSchema)` to create a new message.
  */
 export const FreightSchema: GenMessage<Freight> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 17);
+  messageDesc(file_api_v1alpha1_generated, 18);
 
 /**
  * FreightCollection is a collection of FreightReferences, each of which
@@ -800,7 +838,7 @@ export type FreightCollection = Message<"github.com.akuity.kargo.api.v1alpha1.Fr
  * Use `create(FreightCollectionSchema)` to create a new message.
  */
 export const FreightCollectionSchema: GenMessage<FreightCollection> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 18);
+  messageDesc(file_api_v1alpha1_generated, 19);
 
 /**
  * FreightList is a list of Freight resources.
@@ -824,7 +862,7 @@ export type FreightList = Message<"github.com.akuity.kargo.api.v1alpha1.FreightL
  * Use `create(FreightListSchema)` to create a new message.
  */
 export const FreightListSchema: GenMessage<FreightList> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 19);
+  messageDesc(file_api_v1alpha1_generated, 20);
 
 /**
  * FreightOrigin describes a kind of Freight in terms of where it may have
@@ -861,7 +899,7 @@ export type FreightOrigin = Message<"github.com.akuity.kargo.api.v1alpha1.Freigh
  * Use `create(FreightOriginSchema)` to create a new message.
  */
 export const FreightOriginSchema: GenMessage<FreightOrigin> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 20);
+  messageDesc(file_api_v1alpha1_generated, 21);
 
 /**
  * FreightReference is a simplified representation of a piece of Freight -- not
@@ -913,7 +951,7 @@ export type FreightReference = Message<"github.com.akuity.kargo.api.v1alpha1.Fre
  * Use `create(FreightReferenceSchema)` to create a new message.
  */
 export const FreightReferenceSchema: GenMessage<FreightReference> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 21);
+  messageDesc(file_api_v1alpha1_generated, 22);
 
 /**
  * FreightRequest expresses a Stage's need for Freight having originated from a
@@ -946,7 +984,7 @@ export type FreightRequest = Message<"github.com.akuity.kargo.api.v1alpha1.Freig
  * Use `create(FreightRequestSchema)` to create a new message.
  */
 export const FreightRequestSchema: GenMessage<FreightRequest> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 22);
+  messageDesc(file_api_v1alpha1_generated, 23);
 
 /**
  * @generated from message github.com.akuity.kargo.api.v1alpha1.FreightSources
@@ -1006,7 +1044,7 @@ export type FreightSources = Message<"github.com.akuity.kargo.api.v1alpha1.Freig
  * Use `create(FreightSourcesSchema)` to create a new message.
  */
 export const FreightSourcesSchema: GenMessage<FreightSources> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 23);
+  messageDesc(file_api_v1alpha1_generated, 24);
 
 /**
  * FreightStatus describes a piece of Freight's most recently observed state.
@@ -1045,7 +1083,7 @@ export type FreightStatus = Message<"github.com.akuity.kargo.api.v1alpha1.Freigh
  * Use `create(FreightStatusSchema)` to create a new message.
  */
 export const FreightStatusSchema: GenMessage<FreightStatus> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 24);
+  messageDesc(file_api_v1alpha1_generated, 25);
 
 /**
  * GitCommit describes a specific commit from a specific Git repository.
@@ -1111,7 +1149,7 @@ export type GitCommit = Message<"github.com.akuity.kargo.api.v1alpha1.GitCommit"
  * Use `create(GitCommitSchema)` to create a new message.
  */
 export const GitCommitSchema: GenMessage<GitCommit> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 25);
+  messageDesc(file_api_v1alpha1_generated, 26);
 
 /**
  * GitDiscoveryResult represents the result of a Git discovery operation for a
@@ -1147,7 +1185,7 @@ export type GitDiscoveryResult = Message<"github.com.akuity.kargo.api.v1alpha1.G
  * Use `create(GitDiscoveryResultSchema)` to create a new message.
  */
 export const GitDiscoveryResultSchema: GenMessage<GitDiscoveryResult> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 26);
+  messageDesc(file_api_v1alpha1_generated, 27);
 
 /**
  * GitSubscription defines a subscription to a Git repository.
@@ -1318,7 +1356,7 @@ export type GitSubscription = Message<"github.com.akuity.kargo.api.v1alpha1.GitS
  * Use `create(GitSubscriptionSchema)` to create a new message.
  */
 export const GitSubscriptionSchema: GenMessage<GitSubscription> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 27);
+  messageDesc(file_api_v1alpha1_generated, 28);
 
 /**
  * Health describes the health of a Stage.
@@ -1362,7 +1400,7 @@ export type Health = Message<"github.com.akuity.kargo.api.v1alpha1.Health"> & {
  * Use `create(HealthSchema)` to create a new message.
  */
 export const HealthSchema: GenMessage<Health> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 28);
+  messageDesc(file_api_v1alpha1_generated, 29);
 
 /**
  * HealthCheckStep describes a health check directive which can be executed by
@@ -1393,7 +1431,7 @@ export type HealthCheckStep = Message<"github.com.akuity.kargo.api.v1alpha1.Heal
  * Use `create(HealthCheckStepSchema)` to create a new message.
  */
 export const HealthCheckStepSchema: GenMessage<HealthCheckStep> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 29);
+  messageDesc(file_api_v1alpha1_generated, 30);
 
 /**
  * Image describes a specific version of a container image.
@@ -1446,7 +1484,7 @@ export type Image = Message<"github.com.akuity.kargo.api.v1alpha1.Image"> & {
  * Use `create(ImageSchema)` to create a new message.
  */
 export const ImageSchema: GenMessage<Image> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 30);
+  messageDesc(file_api_v1alpha1_generated, 31);
 
 /**
  * ImageDiscoveryResult represents the result of an image discovery operation
@@ -1492,7 +1530,7 @@ export type ImageDiscoveryResult = Message<"github.com.akuity.kargo.api.v1alpha1
  * Use `create(ImageDiscoveryResultSchema)` to create a new message.
  */
 export const ImageDiscoveryResultSchema: GenMessage<ImageDiscoveryResult> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 31);
+  messageDesc(file_api_v1alpha1_generated, 32);
 
 /**
  * ImageSubscription defines a subscription to an image repository.
@@ -1639,7 +1677,7 @@ export type ImageSubscription = Message<"github.com.akuity.kargo.api.v1alpha1.Im
  * Use `create(ImageSubscriptionSchema)` to create a new message.
  */
 export const ImageSubscriptionSchema: GenMessage<ImageSubscription> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 32);
+  messageDesc(file_api_v1alpha1_generated, 33);
 
 /**
  * Project is a resource type that reconciles to a specially labeled namespace
@@ -1673,7 +1711,7 @@ export type Project = Message<"github.com.akuity.kargo.api.v1alpha1.Project"> &
  * Use `create(ProjectSchema)` to create a new message.
  */
 export const ProjectSchema: GenMessage<Project> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 33);
+  messageDesc(file_api_v1alpha1_generated, 34);
 
 /**
  * ProjectList is a list of Project resources.
@@ -1697,7 +1735,7 @@ export type ProjectList = Message<"github.com.akuity.kargo.api.v1alpha1.ProjectL
  * Use `create(ProjectListSchema)` to create a new message.
  */
 export const ProjectListSchema: GenMessage<ProjectList> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 34);
+  messageDesc(file_api_v1alpha1_generated, 35);
 
 /**
  * ProjectSpec describes a Project.
@@ -1719,7 +1757,7 @@ export type ProjectSpec = Message<"github.com.akuity.kargo.api.v1alpha1.ProjectS
  * Use `create(ProjectSpecSchema)` to create a new message.
  */
 export const ProjectSpecSchema: GenMessage<ProjectSpec> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 35);
+  messageDesc(file_api_v1alpha1_generated, 36);
 
 /**
  * ProjectStatus describes a Project's current status.
@@ -1765,7 +1803,7 @@ export type ProjectStatus = Message<"github.com.akuity.kargo.api.v1alpha1.Projec
  * Use `create(ProjectStatusSchema)` to create a new message.
  */
 export const ProjectStatusSchema: GenMessage<ProjectStatus> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 36);
+  messageDesc(file_api_v1alpha1_generated, 37);
 
 /**
  * Promotion represents a request to transition a particular Stage into a
@@ -1803,7 +1841,7 @@ export type Promotion = Message<"github.com.akuity.kargo.api.v1alpha1.Promotion"
  * Use `create(PromotionSchema)` to create a new message.
  */
 export const PromotionSchema: GenMessage<Promotion> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 37);
+  messageDesc(file_api_v1alpha1_generated, 38);
 
 /**
  * PromotionList contains a list of Promotion
@@ -1827,7 +1865,7 @@ export type PromotionList = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
  * Use `create(PromotionListSchema)` to create a new message.
  */
 export const PromotionListSchema: GenMessage<PromotionList> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 38);
+  messageDesc(file_api_v1alpha1_generated, 39);
 
 /**
  * PromotionPolicy defines policies governing the promotion of Freight to a
@@ -1863,7 +1901,7 @@ export type PromotionPolicy = Message<"github.com.akuity.kargo.api.v1alpha1.Prom
  * Use `create(PromotionPolicySchema)` to create a new message.
  */
 export const PromotionPolicySchema: GenMessage<PromotionPolicy> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 39);
+  messageDesc(file_api_v1alpha1_generated, 40);
 
 /**
  * PromotionReference contains the relevant information about a Promotion
@@ -1906,7 +1944,7 @@ export type PromotionReference = Message<"github.com.akuity.kargo.api.v1alpha1.P
  * Use `create(PromotionReferenceSchema)` to create a new message.
  */
 export const PromotionReferenceSchema: GenMessage<PromotionReference> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 40);
+  messageDesc(file_api_v1alpha1_generated, 41);
 
 /**
  * PromotionSpec describes the desired transition of a specific Stage into a
@@ -1946,9 +1984,9 @@ export type PromotionSpec = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
    * Vars is a list of variables that can be referenced by expressions in
    * promotion steps.
    *
-   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.PromotionVariable vars = 4;
+   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.ExpressionVariable vars = 4;
    */
-  vars: PromotionVariable[];
+  vars: ExpressionVariable[];
 
   /**
    * Steps specifies the directives to be executed as part of this Promotion.
@@ -1969,7 +2007,7 @@ export type PromotionSpec = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
  * Use `create(PromotionSpecSchema)` to create a new message.
  */
 export const PromotionSpecSchema: GenMessage<PromotionSpec> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 41);
+  messageDesc(file_api_v1alpha1_generated, 42);
 
 /**
  * PromotionStatus describes the current state of the transition represented by
@@ -2067,7 +2105,7 @@ export type PromotionStatus = Message<"github.com.akuity.kargo.api.v1alpha1.Prom
  * Use `create(PromotionStatusSchema)` to create a new message.
  */
 export const PromotionStatusSchema: GenMessage<PromotionStatus> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 42);
+  messageDesc(file_api_v1alpha1_generated, 43);
 
 /**
  * PromotionStep describes a directive to be executed as part of a Promotion.
@@ -2122,9 +2160,9 @@ export type PromotionStep = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
    * the step's Config. The values override the values specified in the
    * PromotionSpec.
    *
-   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.PromotionVariable vars = 6;
+   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.ExpressionVariable vars = 6;
    */
-  vars: PromotionVariable[];
+  vars: ExpressionVariable[];
 
   /**
    * Config is opaque configuration for the PromotionStep that is understood
@@ -2142,7 +2180,7 @@ export type PromotionStep = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
  * Use `create(PromotionStepSchema)` to create a new message.
  */
 export const PromotionStepSchema: GenMessage<PromotionStep> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 43);
+  messageDesc(file_api_v1alpha1_generated, 44);
 
 /**
  * PromotionStepRetry describes the retry policy for a PromotionStep.
@@ -2201,7 +2239,7 @@ export type PromotionStepRetry = Message<"github.com.akuity.kargo.api.v1alpha1.P
  * Use `create(PromotionStepRetrySchema)` to create a new message.
  */
 export const PromotionStepRetrySchema: GenMessage<PromotionStepRetry> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 44);
+  messageDesc(file_api_v1alpha1_generated, 45);
 
 /**
  * @generated from message github.com.akuity.kargo.api.v1alpha1.PromotionTask
@@ -2228,7 +2266,7 @@ export type PromotionTask = Message<"github.com.akuity.kargo.api.v1alpha1.Promot
  * Use `create(PromotionTaskSchema)` to create a new message.
  */
 export const PromotionTaskSchema: GenMessage<PromotionTask> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 45);
+  messageDesc(file_api_v1alpha1_generated, 46);
 
 /**
  * PromotionTaskList contains a list of PromotionTasks.
@@ -2252,7 +2290,7 @@ export type PromotionTaskList = Message<"github.com.akuity.kargo.api.v1alpha1.Pr
  * Use `create(PromotionTaskListSchema)` to create a new message.
  */
 export const PromotionTaskListSchema: GenMessage<PromotionTaskList> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 46);
+  messageDesc(file_api_v1alpha1_generated, 47);
 
 /**
  * PromotionTaskReference describes a reference to a PromotionTask.
@@ -2289,7 +2327,7 @@ export type PromotionTaskReference = Message<"github.com.akuity.kargo.api.v1alph
  * Use `create(PromotionTaskReferenceSchema)` to create a new message.
  */
 export const PromotionTaskReferenceSchema: GenMessage<PromotionTaskReference> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 47);
+  messageDesc(file_api_v1alpha1_generated, 48);
 
 /**
  * @generated from message github.com.akuity.kargo.api.v1alpha1.PromotionTaskSpec
@@ -2300,9 +2338,9 @@ export type PromotionTaskSpec = Message<"github.com.akuity.kargo.api.v1alpha1.Pr
    * values of these variables are the default values that can be
    * overridden by the step referencing the task.
    *
-   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.PromotionVariable vars = 1;
+   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.ExpressionVariable vars = 1;
    */
-  vars: PromotionVariable[];
+  vars: ExpressionVariable[];
 
   /**
    * Steps specifies the directives to be executed as part of this
@@ -2323,7 +2361,7 @@ export type PromotionTaskSpec = Message<"github.com.akuity.kargo.api.v1alpha1.Pr
  * Use `create(PromotionTaskSpecSchema)` to create a new message.
  */
 export const PromotionTaskSpecSchema: GenMessage<PromotionTaskSpec> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 48);
+  messageDesc(file_api_v1alpha1_generated, 49);
 
 /**
  * PromotionTemplate defines a template for a Promotion that can be used to
@@ -2343,7 +2381,7 @@ export type PromotionTemplate = Message<"github.com.akuity.kargo.api.v1alpha1.Pr
  * Use `create(PromotionTemplateSchema)` to create a new message.
  */
 export const PromotionTemplateSchema: GenMessage<PromotionTemplate> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 49);
+  messageDesc(file_api_v1alpha1_generated, 50);
 
 /**
  * PromotionTemplateSpec describes the (partial) specification of a Promotion
@@ -2357,9 +2395,9 @@ export type PromotionTemplateSpec = Message<"github.com.akuity.kargo.api.v1alpha
    * Vars is a list of variables that can be referenced by expressions in
    * promotion steps.
    *
-   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.PromotionVariable vars = 2;
+   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.ExpressionVariable vars = 2;
    */
-  vars: PromotionVariable[];
+  vars: ExpressionVariable[];
 
   /**
    * Steps specifies the directives to be executed as part of a Promotion.
@@ -2379,40 +2417,6 @@ export type PromotionTemplateSpec = Message<"github.com.akuity.kargo.api.v1alpha
  * Use `create(PromotionTemplateSpecSchema)` to create a new message.
  */
 export const PromotionTemplateSpecSchema: GenMessage<PromotionTemplateSpec> = /*@__PURE__*/
-  messageDesc(file_api_v1alpha1_generated, 50);
-
-/**
- * PromotionVariable describes a single variable that may be referenced by
- * expressions in promotion steps.
- *
- * @generated from message github.com.akuity.kargo.api.v1alpha1.PromotionVariable
- */
-export type PromotionVariable = Message<"github.com.akuity.kargo.api.v1alpha1.PromotionVariable"> & {
-  /**
-   * Name is the name of the variable.
-   *
-   * +kubebuilder:validation:MinLength=1
-   * +kubebuilder:validation:Pattern=^[a-zA-Z_]\w*$
-   *
-   * @generated from field: optional string name = 1;
-   */
-  name: string;
-
-  /**
-   * Value is the value of the variable. It is allowed to utilize expressions
-   * in the value.
-   * See https://docs.kargo.io/user-guide/reference-docs/expressions for details.
-   *
-   * @generated from field: optional string value = 2;
-   */
-  value: string;
-};
-
-/**
- * Describes the message github.com.akuity.kargo.api.v1alpha1.PromotionVariable.
- * Use `create(PromotionVariableSchema)` to create a new message.
- */
-export const PromotionVariableSchema: GenMessage<PromotionVariable> = /*@__PURE__*/
   messageDesc(file_api_v1alpha1_generated, 51);
 
 /**
@@ -2529,6 +2533,15 @@ export type StageSpec = Message<"github.com.akuity.kargo.api.v1alpha1.StageSpec"
    */
   shard: string;
 
+  /**
+   * Vars is a list of variables that can be referenced anywhere in the
+   * StageSpec that supports expressions. For example, the PromotionTemplate
+   * and arguments of the Verification.
+   *
+   * @generated from field: repeated github.com.akuity.kargo.api.v1alpha1.ExpressionVariable vars = 7;
+   */
+  vars: ExpressionVariable[];
+
   /**
    * RequestedFreight expresses the Stage's need for certain pieces of Freight,
    * each having originated from a particular Warehouse. This list must be
diff --git a/ui/src/gen/directives/helm-template-config.json b/ui/src/gen/directives/helm-template-config.json
index c5f99095..a85e35b7 100644
--- a/ui/src/gen/directives/helm-template-config.json
+++ b/ui/src/gen/directives/helm-template-config.json
@@ -1,6 +1,23 @@
 {
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "HelmTemplateConfig",
+ "definitions": {
+  "setValues": {
+   "type": "object",
+   "additionalProperties": false,
+   "properties": {
+    "key": {
+     "type": "string",
+     "description": "The key whose value should be set. For nested values, use dots to delimit key parts. e.g. `image.tag`.",
+     "minLength": 1
+    },
+    "value": {
+     "type": "string",
+     "description": "The new value for the key."
+    }
+   }
+  }
+ },
  "type": "object",
  "additionalProperties": false,
  "properties": {
@@ -36,6 +53,25 @@
     "minLength": 1
    }
   },
+  "setValues": {
+   "type": "array",
+   "description": "Allows for amending chart configuration inline as one would with the `helm template` command's `--set` flag.",
+   "items": {
+    "type": "object",
+    "additionalProperties": false,
+    "properties": {
+     "key": {
+      "type": "string",
+      "description": "The key whose value should be set. For nested values, use dots to delimit key parts. e.g. `image.tag`.",
+      "minLength": 1
+     },
+     "value": {
+      "type": "string",
+      "description": "The new value for the key."
+     }
+    }
+   }
+  },
   "includeCRDs": {
    "type": "boolean",
    "description": "Whether to include CRDs in the rendered manifests.",
diff --git a/ui/src/gen/schema/clusterpromotiontasks.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/clusterpromotiontasks.kargo.akuity.io_v1alpha1.json
index 33108dcf..d53e31b1 100644
--- a/ui/src/gen/schema/clusterpromotiontasks.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/clusterpromotiontasks.kargo.akuity.io_v1alpha1.json
@@ -81,7 +81,7 @@
               "vars": {
                 "description": "Vars is a list of variables that can be referenced by expressions in\nthe step's Config. The values override the values specified in the\nPromotionSpec.",
                 "items": {
-                  "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+                  "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
                   "properties": {
                     "name": {
                       "description": "Name is the name of the variable.",
@@ -116,7 +116,7 @@
         "vars": {
           "description": "Vars specifies the variables available to the PromotionTask. The\nvalues of these variables are the default values that can be\noverridden by the step referencing the task.",
           "items": {
-            "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+            "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
             "properties": {
               "name": {
                 "description": "Name is the name of the variable.",
diff --git a/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json
index 3321b59d..32005620 100644
--- a/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json
@@ -96,7 +96,7 @@
               "vars": {
                 "description": "Vars is a list of variables that can be referenced by expressions in\nthe step's Config. The values override the values specified in the\nPromotionSpec.",
                 "items": {
-                  "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+                  "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
                   "properties": {
                     "name": {
                       "description": "Name is the name of the variable.",
@@ -131,7 +131,7 @@
         "vars": {
           "description": "Vars is a list of variables that can be referenced by expressions in\npromotion steps.",
           "items": {
-            "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+            "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
             "properties": {
               "name": {
                 "description": "Name is the name of the variable.",
diff --git a/ui/src/gen/schema/promotiontasks.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/promotiontasks.kargo.akuity.io_v1alpha1.json
index 396f2e15..cfc7be8d 100644
--- a/ui/src/gen/schema/promotiontasks.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/promotiontasks.kargo.akuity.io_v1alpha1.json
@@ -81,7 +81,7 @@
               "vars": {
                 "description": "Vars is a list of variables that can be referenced by expressions in\nthe step's Config. The values override the values specified in the\nPromotionSpec.",
                 "items": {
-                  "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+                  "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
                   "properties": {
                     "name": {
                       "description": "Name is the name of the variable.",
@@ -116,7 +116,7 @@
         "vars": {
           "description": "Vars specifies the variables available to the PromotionTask. The\nvalues of these variables are the default values that can be\noverridden by the step referencing the task.",
           "items": {
-            "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+            "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
             "properties": {
               "name": {
                 "description": "Name is the name of the variable.",
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 c2cb4eb0..0548d430 100644
--- a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
@@ -88,7 +88,7 @@
                       "vars": {
                         "description": "Vars is a list of variables that can be referenced by expressions in\nthe step's Config. The values override the values specified in the\nPromotionSpec.",
                         "items": {
-                          "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+                          "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
                           "properties": {
                             "name": {
                               "description": "Name is the name of the variable.",
@@ -123,7 +123,7 @@
                 "vars": {
                   "description": "Vars is a list of variables that can be referenced by expressions in\npromotion steps.",
                   "items": {
-                    "description": "PromotionVariable describes a single variable that may be referenced by\nexpressions in promotion steps.",
+                    "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
                     "properties": {
                       "name": {
                         "description": "Name is the name of the variable.",
@@ -223,6 +223,29 @@
           "description": "Shard is the name of the shard that this Stage belongs to. This is an\noptional field. If not specified, the Stage will belong to the default\nshard. A defaulting webhook will sync the value of the\nkargo.akuity.io/shard label with the value of this field. When this field\nis empty, the webhook will ensure that label is absent.",
           "type": "string"
         },
+        "vars": {
+          "description": "Vars is a list of variables that can be referenced anywhere in the\nStageSpec that supports expressions. For example, the PromotionTemplate\nand arguments of the Verification.",
+          "items": {
+            "description": "ExpressionVariable describes a single variable that may be referenced by\nexpressions in the context of a ClusterPromotionTask, PromotionTask,\nPromotion, AnalysisRun arguments, or other objects that support expressions.\n\nIt is used to pass information to the expression evaluation engine, and to\nallow for dynamic evaluation of expressions based on the variable values.",
+            "properties": {
+              "name": {
+                "description": "Name is the name of the variable.",
+                "minLength": 1,
+                "pattern": "^[a-zA-Z_]\\w*$",
+                "type": "string"
+              },
+              "value": {
+                "description": "Value is the value of the variable. It is allowed to utilize expressions\nin the value.\nSee https://docs.kargo.io/user-guide/reference-docs/expressions for details.",
+                "type": "string"
+              }
+            },
+            "required": [
+              "name"
+            ],
+            "type": "object"
+          },
+          "type": "array"
+        },
         "verification": {
           "description": "Verification describes how to verify a Stage's current Freight is fit for\npromotion downstream.",
           "properties": {
diff --git a/ui/src/pages/settings.tsx b/ui/src/pages/settings.tsx
index e2b31769..d058be7c 100644
--- a/ui/src/pages/settings.tsx
+++ b/ui/src/pages/settings.tsx
@@ -1,75 +1,65 @@
 import { faBarChart, faTasks } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Layout, Menu, MenuProps } from 'antd';
-import { useNavigate, generatePath } from 'react-router-dom';
+import { Breadcrumb, Flex, Menu, Typography } from 'antd';
+import { NavLink, Routes, Route, Navigate, useLocation } from 'react-router-dom';
 
-import { paths } from '@ui/config/paths';
-import { PageTitle } from '@ui/features/common';
+import { BaseHeader } from '@ui/features/common/layout/base-header';
 import { ClusterAnalysisTemplatesList } from '@ui/features/settings/analysis-templates/analysis-templates';
 import { ClusterPromotionTasks } from '@ui/features/settings/cluster-promotion-tasks/cluster-promotion-tasks';
 
-const { Sider, Content } = Layout;
-
-type MenuItem = Required<MenuProps>['items'][number];
-
-export const Settings = ({ section = 'verification' }: { section?: string }) => {
-  const navigate = useNavigate();
-
-  const items: MenuItem[] = [
-    {
-      key: 'verification',
-      label: 'Verification',
-      icon: <FontAwesomeIcon icon={faBarChart} />,
-      onClick: () => {
-        navigate(generatePath(paths.settingsAnalysisTemplates));
-      }
-    },
-    {
-      key: 'cluster-promotion-tasks',
-      label: 'ClusterPromotionTasks',
-      icon: <FontAwesomeIcon icon={faTasks} />,
-      onClick: () => {
-        navigate(generatePath(paths.settingsClusterPromotionTasks));
-      }
-    }
-  ];
+const settingsViews = {
+  verification: {
+    label: 'Verificaion',
+    icon: faBarChart,
+    path: 'analysis-templates',
+    component: ClusterAnalysisTemplatesList
+  },
+  clusterPromotionTasks: {
+    label: 'ClusterPromotionTasks',
+    icon: faTasks,
+    path: 'cluster-promotion-tasks',
+    component: ClusterPromotionTasks
+  }
+};
 
-  const renderSection = (section: string) => {
-    switch (section) {
-      case 'verification':
-        return <ClusterAnalysisTemplatesList />;
-      case 'cluster-promotion-tasks':
-        return <ClusterPromotionTasks />;
-    }
-  };
+const defaultView = settingsViews.verification;
 
-  const getSectionTitle = (section: string) => {
-    switch (section) {
-      case 'verification':
-        return 'Cluster Analysis Templates';
-      case 'cluster-promotion-tasks':
-        return 'Cluster Promotion Tasks';
-    }
-  };
+export const Settings = () => {
+  const location = useLocation();
 
   return (
-    <Layout className='min-h-screen'>
-      <Sider
-        width={250}
-        className='border-r border-gray-300 shadow-sm'
-        style={{ background: 'white' }}
-      >
-        <div className='p-4'>
-          <PageTitle title='Settings' />
-        </div>
-        <Menu mode='vertical' defaultSelectedKeys={[section]} items={items} />
-      </Sider>
-      <Layout>
-        <Content className='p-6 bg-gray-100'>
-          <div className='text-2xl font-semibold mb-4'>{getSectionTitle(section)}</div>
-          {renderSection(section)}
-        </Content>
-      </Layout>
-    </Layout>
+    <>
+      <BaseHeader>
+        <Breadcrumb separator='>' items={[{ title: 'Settings' }]} />
+      </BaseHeader>
+      <div className='py-4 px-6'>
+        <Typography.Title level={3}>Settings</Typography.Title>
+        <Flex gap={24} className='mt-2'>
+          <div style={{ width: 240 }}>
+            <Menu
+              className='-ml-2 -mt-1'
+              style={{ border: 0, background: 'transparent' }}
+              selectedKeys={Object.values(settingsViews)
+                .map((i) => i.path)
+                .filter((i) => location.pathname.endsWith(i))}
+              items={Object.values(settingsViews).map((i) => ({
+                label: <NavLink to={`../${i.path}`}>{i.label}</NavLink>,
+                icon: <FontAwesomeIcon icon={i.icon} />,
+                key: i.path
+              }))}
+            />
+          </div>
+          <div className='flex-1 overflow-hidden' style={{ maxWidth: '920px', minHeight: '700px' }}>
+            <Routes>
+              <Route index element={<Navigate to={defaultView.path} replace={true} />} />
+              {Object.values(settingsViews).map((t) => (
+                <Route key={t.path} path={t.path} element={<t.component />} />
+              ))}
+              <Route path='*' element={<Navigate to='../' replace={true} />} />
+            </Routes>
+          </div>
+        </Flex>
+      </div>
+    </>
   );
 };



Test patch:
diff --git a/internal/api/events_test.go b/internal/api/events_test.go
new file mode 100644
index 00000000..68895b64
--- /dev/null
+++ b/internal/api/events_test.go
@@ -0,0 +1,54 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	kargoapi "github.com/akuity/kargo/api/v1alpha1"
+	"github.com/akuity/kargo/internal/server/user"
+)
+
+func TestFormatEventUserActor(t *testing.T) {
+	for _, test := range []struct {
+		name     string
+		user     user.Info
+		expected string
+	}{
+		{
+			name:     "admin",
+			user:     user.Info{IsAdmin: true},
+			expected: kargoapi.EventActorAdmin,
+		},
+		{
+			name: "sub",
+			user: user.Info{
+				Claims: map[string]any{
+					"sub": "subject",
+				},
+			},
+			expected: kargoapi.EventActorSubjectPrefix + "subject",
+		},
+		{
+			name: "email",
+			user: user.Info{
+				Claims: map[string]any{
+					"email": "email@inbox.com",
+				},
+			},
+			expected: kargoapi.EventActorEmailPrefix + "email@inbox.com",
+		},
+		{
+			name: "oidc-username",
+			user: user.Info{
+				Username: "oidc-username",
+			},
+			expected: formatOIDCUsername("oidc-username"),
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			result := FormatEventUserActor(test.user)
+			require.Equal(t, test.expected, result)
+		})
+	}
+}
diff --git a/internal/controller/freight/finder_test.go b/internal/controller/freight/finder_test.go
index 2df033e5..4ee9a259 100644
--- a/internal/controller/freight/finder_test.go
+++ b/internal/controller/freight/finder_test.go
@@ -59,8 +59,10 @@ func TestFindCommit(t *testing.T) {
 					Commits: []kargoapi.GitCommit{testCommit2},
 				},
 			},
-			assertions: func(t *testing.T, _ *kargoapi.GitCommit, err error) {
-				require.ErrorContains(t, err, "not found in referenced Freight")
+			assertions: func(t *testing.T, commit *kargoapi.GitCommit, err error) {
+				require.NoError(t, err)
+				require.Nil(t, commit)
+
 			},
 		},
 		{
@@ -127,8 +129,9 @@ func TestFindCommit(t *testing.T) {
 					RequestedFreight: []kargoapi.FreightRequest{{Origin: testOrigin1}},
 				},
 			},
-			assertions: func(t *testing.T, _ *kargoapi.GitCommit, err error) {
-				require.ErrorContains(t, err, "not found in referenced Freight")
+			assertions: func(t *testing.T, commit *kargoapi.GitCommit, err error) {
+				require.NoError(t, err)
+				require.Nil(t, commit)
 			},
 		},
 		{
@@ -293,8 +296,9 @@ func TestFindImage(t *testing.T) {
 					Images: []kargoapi.Image{testImage2},
 				},
 			},
-			assertions: func(t *testing.T, _ *kargoapi.Image, err error) {
-				require.ErrorContains(t, err, "not found in referenced Freight")
+			assertions: func(t *testing.T, image *kargoapi.Image, err error) {
+				require.NoError(t, err)
+				require.Nil(t, image)
 			},
 		},
 		{
@@ -359,8 +363,9 @@ func TestFindImage(t *testing.T) {
 					RequestedFreight: []kargoapi.FreightRequest{{Origin: testOrigin1}},
 				},
 			},
-			assertions: func(t *testing.T, _ *kargoapi.Image, err error) {
-				require.ErrorContains(t, err, "not found in referenced Freight")
+			assertions: func(t *testing.T, image *kargoapi.Image, err error) {
+				require.NoError(t, err)
+				require.Nil(t, image)
 			},
 		},
 		{
@@ -656,8 +661,9 @@ func TestFindChart(t *testing.T) {
 					Charts: []kargoapi.Chart{testChart2},
 				},
 			},
-			assertions: func(t *testing.T, _ *kargoapi.Chart, err error) {
-				require.ErrorContains(t, err, "not found in referenced Freight")
+			assertions: func(t *testing.T, chart *kargoapi.Chart, err error) {
+				require.NoError(t, err)
+				require.Nil(t, chart)
 			},
 		},
 		{
diff --git a/internal/indexer/indexer_test.go b/internal/indexer/indexer_test.go
index 7e4ae63b..9d552681 100644
--- a/internal/indexer/indexer_test.go
+++ b/internal/indexer/indexer_test.go
@@ -9,8 +9,8 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	rbacapi "github.com/akuity/kargo/api/rbac/v1alpha1"
 	kargoapi "github.com/akuity/kargo/api/v1alpha1"
@@ -329,7 +329,7 @@ func TestRunningPromotionsByArgoCDApplications(t *testing.T) {
 				},
 				Spec: kargoapi.PromotionSpec{
 					Stage: "fake-stage",
-					Vars: []kargoapi.PromotionVariable{
+					Vars: []kargoapi.ExpressionVariable{
 						{
 							Name:  "app",
 							Value: "fake-app-from-var",
@@ -361,7 +361,7 @@ func TestRunningPromotionsByArgoCDApplications(t *testing.T) {
 						},
 						{
 							Uses: "argocd-update",
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{
 									Name:  "app",
 									Value: "fake-app-from-step-var",
@@ -381,7 +381,7 @@ func TestRunningPromotionsByArgoCDApplications(t *testing.T) {
 						},
 						{
 							Uses: "argocd-update",
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{
 									Name:  "input",
 									Value: "${{ outputs.composition.name }}",
@@ -449,13 +449,12 @@ func TestRunningPromotionsByArgoCDApplications(t *testing.T) {
 	}
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
-			scheme := runtime.NewScheme()
-			require.NoError(t, kargoapi.AddToScheme(scheme))
 			require.Equal(
 				t,
 				testCase.expected,
 				RunningPromotionsByArgoCDApplications(
 					context.TODO(),
+					fake.NewClientBuilder().Build(),
 					testCase.shardName,
 				)(testCase.obj),
 			)
diff --git a/internal/kargo/promotion_builder_test.go b/internal/kargo/promotion_builder_test.go
index 8f1a5947..666c6be0 100644
--- a/internal/kargo/promotion_builder_test.go
+++ b/internal/kargo/promotion_builder_test.go
@@ -82,9 +82,12 @@ func TestPromotionBuilder_Build(t *testing.T) {
 					Namespace: "test-project",
 				},
 				Spec: kargoapi.StageSpec{
+					Vars: []kargoapi.ExpressionVariable{
+						{Name: "key0", Value: "value0"},
+					},
 					PromotionTemplate: &kargoapi.PromotionTemplate{
 						Spec: kargoapi.PromotionTemplateSpec{
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{Name: "key1", Value: "value1"},
 							},
 							Steps: []kargoapi.PromotionStep{
@@ -108,7 +111,11 @@ func TestPromotionBuilder_Build(t *testing.T) {
 				assert.Equal(t, "abc123", promotion.Spec.Freight)
 
 				// Check vars
-				assert.Equal(t, []kargoapi.PromotionVariable{
+				assert.Equal(t, []kargoapi.ExpressionVariable{
+					{
+						Name:  "key0",
+						Value: "value0",
+					},
 					{
 						Name:  "key1",
 						Value: "value1",
@@ -141,7 +148,7 @@ func TestPromotionBuilder_Build(t *testing.T) {
 									Task: &kargoapi.PromotionTaskReference{
 										Name: "test-task",
 									},
-									Vars: []kargoapi.PromotionVariable{
+									Vars: []kargoapi.ExpressionVariable{
 										{Name: "input1", Value: "value1"},
 									},
 								},
@@ -252,7 +259,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 							Task: &kargoapi.PromotionTaskReference{
 								Name: "test-task",
 							},
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{Name: "input1", Value: "value1"},
 							},
 						},
@@ -266,7 +273,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 						},
 						Steps: []kargoapi.PromotionStep{
@@ -289,7 +296,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 				// Check inflated task step
 				assert.Equal(t, "task-step::sub-step", steps[1].As)
 				assert.Equal(t, "other-fake-step", steps[1].Uses)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "value1"},
 				}, steps[1].Vars)
 			},
@@ -308,7 +315,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 							Task: &kargoapi.PromotionTaskReference{
 								Name: "test-task-1",
 							},
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{Name: "input1", Value: "value1"},
 							},
 						},
@@ -318,7 +325,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 								Kind: "ClusterPromotionTask",
 								Name: "test-task-2",
 							},
-							Vars: []kargoapi.PromotionVariable{
+							Vars: []kargoapi.ExpressionVariable{
 								{Name: "input2", Value: "value2"},
 							},
 						},
@@ -332,7 +339,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 						},
 						Steps: []kargoapi.PromotionStep{
@@ -348,7 +355,7 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 						Name: "test-task-2",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input2"},
 						},
 						Steps: []kargoapi.PromotionStep{
@@ -366,13 +373,13 @@ func TestPromotionBuilder_InflateSteps(t *testing.T) {
 
 				assert.Equal(t, "task1::step1", steps[0].As)
 				assert.Equal(t, "fake-step", steps[0].Uses)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "value1"},
 				}, steps[0].Vars)
 
 				assert.Equal(t, "task2::step2", steps[1].As)
 				assert.Equal(t, "other-fake-step", steps[1].Uses)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input2", Value: "value2"},
 				}, steps[1].Vars)
 			},
@@ -402,7 +409,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 		name       string
 		project    string
 		taskAlias  string
-		promoVars  []kargoapi.PromotionVariable
+		promoVars  []kargoapi.ExpressionVariable
 		taskStep   kargoapi.PromotionStep
 		objects    []client.Object
 		assertions func(*testing.T, []kargoapi.PromotionStep, error)
@@ -439,7 +446,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 						},
 					},
@@ -454,14 +461,14 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 			name:      "successful task step inflation",
 			project:   "test-project",
 			taskAlias: "task-1",
-			promoVars: []kargoapi.PromotionVariable{
+			promoVars: []kargoapi.ExpressionVariable{
 				{Name: "input3", Value: "value1"},
 			},
 			taskStep: kargoapi.PromotionStep{
 				Task: &kargoapi.PromotionTaskReference{
 					Name: "test-task",
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "value1"},
 					{Name: "input2", Value: "value2"},
 				},
@@ -473,7 +480,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 							{Name: "input2", Value: "default2"},
 							{Name: "input3"},
@@ -486,7 +493,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 							{
 								As:   "step2",
 								Uses: "other-fake-step",
-								Vars: []kargoapi.PromotionVariable{
+								Vars: []kargoapi.ExpressionVariable{
 									{Name: "input4", Value: "value4"},
 								},
 							},
@@ -500,7 +507,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 
 				assert.Equal(t, "task-1::step1", steps[0].As)
 				assert.Equal(t, "fake-step", steps[0].Uses)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input2", Value: "default2"},
 					{Name: "input1", Value: "value1"},
 					{Name: "input2", Value: "value2"},
@@ -508,7 +515,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 
 				assert.Equal(t, "task-1::step2", steps[1].As)
 				assert.Equal(t, "other-fake-step", steps[1].Uses)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input2", Value: "default2"},
 					{Name: "input1", Value: "value1"},
 					{Name: "input2", Value: "value2"},
@@ -524,7 +531,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 				Task: &kargoapi.PromotionTaskReference{
 					Name: "test-task",
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "value1"},
 				},
 			},
@@ -535,7 +542,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 						},
 						Steps: []kargoapi.PromotionStep{
@@ -566,7 +573,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 					Kind: "ClusterPromotionTask",
 					Name: "test-cluster-task",
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "value1"},
 				},
 			},
@@ -576,7 +583,7 @@ func TestPromotionBuilder_inflateTaskSteps(t *testing.T) {
 						Name: "test-cluster-task",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1"},
 						},
 						Steps: []kargoapi.PromotionStep{
@@ -703,7 +710,7 @@ func TestPromotionBuilder_getTaskSpec(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1", Value: "value1"},
 						},
 					},
@@ -731,7 +738,7 @@ func TestPromotionBuilder_getTaskSpec(t *testing.T) {
 						Name: "test-cluster-task",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1", Value: "value1"},
 						},
 					},
@@ -760,7 +767,7 @@ func TestPromotionBuilder_getTaskSpec(t *testing.T) {
 						Namespace: "test-project",
 					},
 					Spec: kargoapi.PromotionTaskSpec{
-						Vars: []kargoapi.PromotionVariable{
+						Vars: []kargoapi.ExpressionVariable{
 							{Name: "input1", Value: "value1"},
 						},
 					},
@@ -937,66 +944,66 @@ func Test_generatePromotionTaskStepName(t *testing.T) {
 func Test_promotionTaskVarsToStepVars(t *testing.T) {
 	tests := []struct {
 		name       string
-		taskVars   []kargoapi.PromotionVariable
-		promoVars  []kargoapi.PromotionVariable
-		stepVars   []kargoapi.PromotionVariable
-		assertions func(t *testing.T, result []kargoapi.PromotionVariable, err error)
+		taskVars   []kargoapi.ExpressionVariable
+		promoVars  []kargoapi.ExpressionVariable
+		stepVars   []kargoapi.ExpressionVariable
+		assertions func(t *testing.T, result []kargoapi.ExpressionVariable, err error)
 	}{
 		{
 			name:     "nil inputs returns nil map and no error",
 			taskVars: nil,
 			stepVars: nil,
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
 				assert.Nil(t, result)
 			},
 		},
 		{
 			name:     "empty inputs returns nil map and no error",
-			taskVars: []kargoapi.PromotionVariable{},
+			taskVars: []kargoapi.ExpressionVariable{},
 			stepVars: nil,
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
 				assert.Nil(t, result)
 			},
 		},
 		{
 			name: "missing required variable returns error",
-			taskVars: []kargoapi.PromotionVariable{
+			taskVars: []kargoapi.ExpressionVariable{
 				{Name: "input1"},
 			},
-			stepVars: []kargoapi.PromotionVariable{
+			stepVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: ""},
 			},
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				assert.ErrorContains(t, err, "missing value for variable \"input1\"")
 				assert.Nil(t, result)
 			},
 		},
 		{
 			name: "default value used when config value not provided",
-			taskVars: []kargoapi.PromotionVariable{
+			taskVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "default1"},
 			},
 			stepVars: nil,
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "default1"},
 				}, result)
 			},
 		},
 		{
 			name: "step value appends task default value",
-			taskVars: []kargoapi.PromotionVariable{
+			taskVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "default1"},
 			},
-			stepVars: []kargoapi.PromotionVariable{
+			stepVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "override1"},
 			},
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "default1"},
 					{Name: "input1", Value: "override1"},
 				}, result)
@@ -1004,14 +1011,14 @@ func Test_promotionTaskVarsToStepVars(t *testing.T) {
 		},
 		{
 			name: "promotion variable overrides default value",
-			taskVars: []kargoapi.PromotionVariable{
+			taskVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "default1"},
 			},
-			promoVars: []kargoapi.PromotionVariable{
+			promoVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "override1"},
 			},
 			stepVars: nil,
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
 				// Variable is set by engine at runtime
 				assert.Empty(t, result)
@@ -1019,18 +1026,18 @@ func Test_promotionTaskVarsToStepVars(t *testing.T) {
 		},
 		{
 			name: "multiple inputs processed correctly",
-			taskVars: []kargoapi.PromotionVariable{
+			taskVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "default1"},
 				{Name: "input2", Value: "default2"},
 				{Name: "input3"},
 			},
-			stepVars: []kargoapi.PromotionVariable{
+			stepVars: []kargoapi.ExpressionVariable{
 				{Name: "input1", Value: "override1"},
 				{Name: "input3", Value: "value3"},
 			},
-			assertions: func(t *testing.T, result []kargoapi.PromotionVariable, err error) {
+			assertions: func(t *testing.T, result []kargoapi.ExpressionVariable, err error) {
 				require.NoError(t, err)
-				assert.ElementsMatch(t, []kargoapi.PromotionVariable{
+				assert.ElementsMatch(t, []kargoapi.ExpressionVariable{
 					{Name: "input1", Value: "default1"},
 					{Name: "input2", Value: "default2"},
 					{Name: "input1", Value: "override1"},
diff --git a/internal/promotion/promotion_test.go b/internal/promotion/promotion_test.go
index fbf669a0..ff823e45 100644
--- a/internal/promotion/promotion_test.go
+++ b/internal/promotion/promotion_test.go
@@ -97,8 +97,8 @@ func TestStep_GetTimeout(t *testing.T) {
 
 func TestStep_GetConfig(t *testing.T) {
 	testScheme := k8sruntime.NewScheme()
-	err := kargoapi.AddToScheme(testScheme)
-	require.NoError(t, err)
+	require.NoError(t, kargoapi.AddToScheme(testScheme))
+
 	testClient := fake.NewClientBuilder().WithScheme(testScheme).WithObjects(
 		&kargoapi.Warehouse{
 			ObjectMeta: v1.ObjectMeta{
@@ -194,7 +194,7 @@ func TestStep_GetConfig(t *testing.T) {
 			name: "test vars with literal values",
 			// Test that vars can be assigned literal values
 			promoCtx: Context{
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "strVar",
 						Value: "foo",
@@ -224,7 +224,7 @@ func TestStep_GetConfig(t *testing.T) {
 			name: "test vars with expressions",
 			// Test using expressions to define the value of vars
 			promoCtx: Context{
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "strVar",
 						Value: "${{ 'f' + 'o' + 'o' }}",
@@ -274,7 +274,7 @@ func TestStep_GetConfig(t *testing.T) {
 			// Test that the warehouse() function can be used to reference freight
 			// origins
 			promoCtx: Context{
-				Vars: []kargoapi.PromotionVariable{{
+				Vars: []kargoapi.ExpressionVariable{{
 					Name:  "warehouseName",
 					Value: "fake-warehouse",
 				}},
@@ -322,7 +322,7 @@ func TestStep_GetConfig(t *testing.T) {
 						},
 					},
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "warehouseName",
 						Value: "fake-warehouse",
@@ -377,7 +377,7 @@ func TestStep_GetConfig(t *testing.T) {
 						},
 					},
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "warehouseName",
 						Value: "fake-warehouse",
@@ -439,7 +439,7 @@ func TestStep_GetConfig(t *testing.T) {
 						},
 					},
 				},
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "warehouseName",
 						Value: "fake-warehouse",
@@ -502,6 +502,384 @@ func TestStep_GetConfig(t *testing.T) {
 		})
 	}
 }
+
+func TestStep_GetVars(t *testing.T) {
+	testScheme := k8sruntime.NewScheme()
+	require.NoError(t, kargoapi.AddToScheme(testScheme))
+
+	testClient := fake.NewClientBuilder().WithScheme(testScheme).WithObjects(
+		&kargoapi.Warehouse{
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "fake-warehouse",
+				Namespace: "fake-project",
+			},
+			Spec: kargoapi.WarehouseSpec{
+				Subscriptions: []kargoapi.RepoSubscription{
+					{
+						Git: &kargoapi.GitSubscription{
+							RepoURL: "https://fake-git-repo",
+						},
+					},
+				},
+			},
+		},
+	).Build()
+
+	testCases := []struct {
+		name         string
+		promoCtx     Context
+		promoState   promotion.State
+		step         Step
+		expectedVars map[string]any
+		expectErr    bool
+	}{
+		{
+			name: "global vars with literal values",
+			promoCtx: Context{
+				Project: "fake-project",
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "str",
+						Value: "foo",
+					},
+					{
+						Name:  "bool",
+						Value: "true",
+					},
+					{
+						Name:  "num",
+						Value: "42",
+					},
+				},
+			},
+			step: Step{},
+			expectedVars: map[string]any{
+				"str":  "foo",
+				"bool": "true",
+				"num":  "42",
+			},
+		},
+		{
+			name: "global vars with expressions",
+			promoCtx: Context{
+				Project: "fake-project",
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "str",
+						Value: "${{ 'f' + 'o' + 'o' }}",
+					},
+					{
+						Name:  "bool",
+						Value: "${{ 1 == 1 }}",
+					},
+					{
+						Name:  "num",
+						Value: "${{ 40 + 2 }}",
+					},
+				},
+			},
+			step: Step{},
+			expectedVars: map[string]any{
+				"str":  "foo",
+				"bool": true,
+				"num":  float64(42),
+			},
+		},
+		{
+			name: "step vars with literal values",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "str",
+						Value: "foo",
+					},
+					{
+						Name:  "bool",
+						Value: "true",
+					},
+					{
+						Name:  "num",
+						Value: "42",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"str":  "foo",
+				"bool": "true",
+				"num":  "42",
+			},
+		},
+		{
+			name: "step vars with expressions",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "str",
+						Value: "${{ 'f' + 'o' + 'o' }}",
+					},
+					{
+						Name:  "bool",
+						Value: "${{ 1 == 1 }}",
+					},
+					{
+						Name:  "num",
+						Value: "${{ 40 + 2 }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"str":  "foo",
+				"bool": true,
+				"num":  float64(42),
+			},
+		},
+		{
+			name: "step vars referencing global vars",
+			promoCtx: Context{
+				Project: "fake-project",
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "global",
+						Value: "global-value",
+					},
+				},
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "local",
+						Value: "${{ vars.global + '-suffix' }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"global": "global-value",
+				"local":  "global-value-suffix",
+			},
+		},
+		{
+			name: "step vars referencing other step vars",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "v1",
+						Value: "value1",
+					},
+					{
+						Name:  "v2",
+						Value: "${{ vars.v1 + '-suffix' }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"v1": "value1",
+				"v2": "value1-suffix",
+			},
+		},
+		{
+			name: "step vars referencing outputs",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			promoState: promotion.State{
+				"output1": "output-value",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "fromOutput",
+						Value: "${{ outputs.output1 + '-suffix' }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"fromOutput": "output-value-suffix",
+			},
+		},
+		{
+			name: "step vars referencing task outputs",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			promoState: promotion.State{
+				"task::alias": map[string]any{
+					"foo": "baz",
+				},
+			},
+			step: Step{
+				Alias: "task::other-alias",
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "fromTask",
+						Value: "${{ task.outputs.alias.foo + '-suffix' }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"fromTask": "baz-suffix",
+			},
+		},
+		{
+			name: "invalid expression in global var",
+			promoCtx: Context{
+				Project: "fake-project",
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "invalid",
+						Value: "${{ invalid.expression }}",
+					},
+				},
+			},
+			step:      Step{},
+			expectErr: true,
+		},
+		{
+			name: "invalid expression in step var",
+			promoCtx: Context{
+				Project: "fake-project",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "invalid",
+						Value: "${{ invalid.expression }}",
+					},
+				},
+			},
+			expectErr: true,
+		},
+		{
+			name: "warehouse function in vars",
+			promoCtx: Context{
+				Project: "fake-project",
+				FreightRequests: []kargoapi.FreightRequest{{
+					Origin: kargoapi.FreightOrigin{
+						Kind: kargoapi.FreightOriginKindWarehouse,
+						Name: "fake-warehouse",
+					},
+				}},
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "wh",
+						Value: "${{ warehouse('fake-warehouse') }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"wh": map[string]any{
+					"kind": string(kargoapi.FreightOriginKindWarehouse),
+					"name": "fake-warehouse",
+				},
+			},
+		},
+		{
+			name: "context properties in vars",
+			promoCtx: Context{
+				Project:   "fake-project",
+				Stage:     "fake-stage",
+				Promotion: "fake-promotion",
+				Actor:     "fake-creator",
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "proj",
+						Value: "${{ ctx.project }}",
+					},
+					{
+						Name:  "stage",
+						Value: "${{ ctx.stage }}",
+					},
+					{
+						Name:  "promo",
+						Value: "${{ ctx.promotion }}",
+					},
+					{
+						Name:  "actor",
+						Value: "${{ ctx.meta.promotion.actor }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"proj":  "fake-project",
+				"stage": "fake-stage",
+				"promo": "fake-promotion",
+				"actor": "fake-creator",
+			},
+		},
+		{
+			name: "freight functions in vars",
+			promoCtx: Context{
+				Project: "fake-project",
+				FreightRequests: []kargoapi.FreightRequest{{
+					Origin: kargoapi.FreightOrigin{
+						Kind: kargoapi.FreightOriginKindWarehouse,
+						Name: "fake-warehouse",
+					},
+					Sources: kargoapi.FreightSources{
+						Direct: true,
+					},
+				}},
+				Freight: kargoapi.FreightCollection{
+					Freight: map[string]kargoapi.FreightReference{
+						"Warehouse/fake-warehouse": {
+							Origin: kargoapi.FreightOrigin{
+								Kind: kargoapi.FreightOriginKindWarehouse,
+								Name: "fake-warehouse",
+							},
+							Commits: []kargoapi.GitCommit{{
+								RepoURL: "https://fake-git-repo",
+								ID:      "fake-commit-id",
+							}},
+						},
+					},
+				},
+			},
+			step: Step{
+				Vars: []kargoapi.ExpressionVariable{
+					{
+						Name:  "commit",
+						Value: "${{ commitFrom('https://fake-git-repo').ID }}",
+					},
+				},
+			},
+			expectedVars: map[string]any{
+				"commit": "fake-commit-id",
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			vars, err := testCase.step.GetVars(
+				context.Background(),
+				testClient,
+				testCase.promoCtx,
+				testCase.promoState,
+			)
+
+			if testCase.expectErr {
+				require.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			require.Equal(t, testCase.expectedVars, vars)
+		})
+	}
+}
+
 func TestStep_Skip(t *testing.T) {
 	tests := []struct {
 		name       string
@@ -524,7 +902,7 @@ func TestStep_Skip(t *testing.T) {
 				If: "${{ vars.foo == 'bar' }}",
 			},
 			ctx: Context{
-				Vars: []kargoapi.PromotionVariable{
+				Vars: []kargoapi.ExpressionVariable{
 					{
 						Name:  "foo",
 						Value: "bar",
@@ -565,6 +943,33 @@ func TestStep_Skip(t *testing.T) {
 				assert.True(t, b)
 			},
 		},
+		{
+			name: "if condition uses expression function",
+			step: &Step{
+				If: "${{ commitFrom('https://git.example.com', warehouse('fake-warehouse')).ID == 'foo' }}",
+			},
+			ctx: Context{
+				Project: "fake-project",
+				Freight: kargoapi.FreightCollection{
+					Freight: map[string]kargoapi.FreightReference{
+						"Warehouse/fake-warehouse": {
+							Origin: kargoapi.FreightOrigin{
+								Kind: kargoapi.FreightOriginKindWarehouse,
+								Name: "fake-warehouse",
+							},
+							Commits: []kargoapi.GitCommit{{
+								RepoURL: "https://git.example.com",
+								ID:      "foo",
+							}},
+						},
+					},
+				},
+			},
+			assertions: func(t *testing.T, b bool, err error) {
+				assert.NoError(t, err)
+				assert.False(t, b)
+			},
+		},
 		{
 			name: "if condition does not evaluate to a boolean",
 			step: &Step{
@@ -578,7 +983,12 @@ func TestStep_Skip(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := tt.step.Skip(tt.ctx, tt.state)
+			got, err := tt.step.Skip(
+				context.Background(),
+				fake.NewClientBuilder().Build(),
+				tt.ctx,
+				tt.state,
+			)
 			tt.assertions(t, got, err)
 		})
 	}
diff --git a/internal/promotion/runner/builtin/helm_template_runner_test.go b/internal/promotion/runner/builtin/helm_template_runner_test.go
index 924f2a88..cbd6819a 100644
--- a/internal/promotion/runner/builtin/helm_template_runner_test.go
+++ b/internal/promotion/runner/builtin/helm_template_runner_test.go
@@ -107,6 +107,53 @@ data:
 # Source: test-chart/templates/test.yaml
 apiVersion: v1
 kind: ConfigMap
+metadata:
+  name: test-release-configmap
+  namespace: test-namespace
+data:
+  value1: value1
+  value2: value2
+`, string(content))
+			},
+		},
+		{
+			name: "successful run with set values",
+			files: map[string]string{
+				"values.yaml": "key1: value1",
+				"charts/test-chart/Chart.yaml": `apiVersion: v1
+name: test-chart
+version: 0.1.0`,
+				"charts/test-chart/templates/test.yaml": `---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Release.Name }}-configmap
+  namespace: {{ .Release.Namespace }}
+data:
+  value1: {{ .Values.key1 }}
+  value2: {{ .Values.key2 }}
+`,
+			},
+			cfg: builtin.HelmTemplateConfig{
+				Path:        "charts/test-chart",
+				ValuesFiles: []string{"values.yaml"},
+				SetValues:   []builtin.SetValues{{Key: "key2", Value: "value2"}},
+				OutPath:     "output.yaml",
+				ReleaseName: "test-release",
+				Namespace:   "test-namespace",
+			},
+			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
+				require.NoError(t, err)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+
+				outPath := filepath.Join(workDir, "output.yaml")
+				require.FileExists(t, outPath)
+				content, err := os.ReadFile(outPath)
+				require.NoError(t, err)
+				assert.Equal(t, `---
+# Source: test-chart/templates/test.yaml
+apiVersion: v1
+kind: ConfigMap
 metadata:
   name: test-release-configmap
   namespace: test-namespace
@@ -302,19 +349,56 @@ func Test_helmTemplateRunner_composeValues(t *testing.T) {
 	tests := []struct {
 		name           string
 		workDir        string
-		valuesFiles    []string
+		cfg            builtin.HelmTemplateConfig
 		valuesContents map[string]string
 		assertions     func(*testing.T, map[string]any, error)
 	}{
 		{
-			name:        "successful composition",
-			workDir:     t.TempDir(),
-			valuesFiles: []string{"values1.yaml", "values2.yaml"},
+			name:    "successful composition",
+			workDir: t.TempDir(),
+			cfg: builtin.HelmTemplateConfig{
+				ValuesFiles: []string{"values1.yaml", "values2.yaml"},
+				SetValues:   []builtin.SetValues{{Key: "key3", Value: "value3"}},
+			},
 			valuesContents: map[string]string{
 				"values1.yaml": "key1: value1",
 				"values2.yaml": "key2: value2",
 			},
 			assertions: func(t *testing.T, result map[string]any, err error) {
+				assert.NoError(t, err)
+				assert.Equal(t, map[string]any{
+					"key1": "value1",
+					"key2": "value2",
+					"key3": "value3",
+				}, result)
+			},
+		},
+		{
+			name:    "successful composition with no files",
+			workDir: t.TempDir(),
+			cfg: builtin.HelmTemplateConfig{
+				ValuesFiles: []string{},
+				SetValues:   []builtin.SetValues{{Key: "key3", Value: "value3"}},
+			},
+			valuesContents: map[string]string{},
+			assertions: func(t *testing.T, result map[string]any, err error) {
+				assert.NoError(t, err)
+				assert.Equal(t, map[string]any{
+					"key3": "value3",
+				}, result)
+			},
+		},
+		{
+			name:    "successful composition with no set values",
+			workDir: t.TempDir(),
+			cfg: builtin.HelmTemplateConfig{
+				ValuesFiles: []string{"values1.yaml", "values2.yaml"},
+				SetValues:   []builtin.SetValues{},
+			},
+			valuesContents: map[string]string{
+				"values1.yaml": "key1: value1",
+				"values2.yaml": "key2: value2",
+			}, assertions: func(t *testing.T, result map[string]any, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, map[string]any{
 					"key1": "value1",
@@ -323,9 +407,12 @@ func Test_helmTemplateRunner_composeValues(t *testing.T) {
 			},
 		},
 		{
-			name:        "file not found",
-			workDir:     t.TempDir(),
-			valuesFiles: []string{"non_existent.yaml"},
+			name:    "file not found",
+			workDir: t.TempDir(),
+			cfg: builtin.HelmTemplateConfig{
+				ValuesFiles: []string{"non_existent.yaml"},
+				SetValues:   []builtin.SetValues{},
+			},
 			assertions: func(t *testing.T, result map[string]any, err error) {
 				assert.Error(t, err)
 				assert.Nil(t, result)
@@ -340,7 +427,7 @@ func Test_helmTemplateRunner_composeValues(t *testing.T) {
 			for p, c := range tt.valuesContents {
 				require.NoError(t, os.WriteFile(filepath.Join(tt.workDir, p), []byte(c), 0o600))
 			}
-			result, err := runner.composeValues(tt.workDir, tt.valuesFiles)
+			result, err := runner.composeValues(tt.workDir, tt.cfg)
 			tt.assertions(t, result, err)
 		})
 	}
diff --git a/internal/rollouts/analysis_run_builder_test.go b/internal/rollouts/analysis_run_builder_test.go
index 852536d1..bf7fb3b0 100644
--- a/internal/rollouts/analysis_run_builder_test.go
+++ b/internal/rollouts/analysis_run_builder_test.go
@@ -624,6 +624,60 @@ func TestAnalysisRunBuilder_buildArgs(t *testing.T) {
 				require.ErrorContains(t, err, "evaluate argument \"param1\"")
 			},
 		},
+		{
+			name: "evaluate variable in argument",
+			template: &rolloutsapi.AnalysisTemplate{
+				Spec: rolloutsapi.AnalysisTemplateSpec{
+					Args: []rolloutsapi.Argument{
+						{Name: "param1"},
+					},
+				},
+			},
+			args: []kargoapi.AnalysisRunArgument{
+				{Name: "param1", Value: "${{ vars.test }}"},
+			},
+			exprCfg: &ArgumentEvaluationConfig{
+				Vars: []ArgumentVariable{
+					{Name: "test", Value: "value1"},
+					// Ensure overriding and expression evaluation works.
+					{Name: "test", Value: "${{ vars.test + '-suffix'}}"},
+				},
+			},
+			assertions: func(t *testing.T, args []rolloutsapi.Argument, err error) {
+				require.NoError(t, err)
+				assert.Len(t, args, 1)
+				assert.Equal(t, "value1-suffix", *args[0].Value)
+			},
+		},
+		{
+			name:     "variable evaluation error",
+			template: &rolloutsapi.AnalysisTemplate{},
+			exprCfg: &ArgumentEvaluationConfig{
+				Vars: []ArgumentVariable{
+					{Name: "test", Value: "${{ ctx.test }}"},
+				},
+			},
+			assertions: func(t *testing.T, _ []rolloutsapi.Argument, err error) {
+				require.ErrorContains(t, err, "evaluate variable \"test\"")
+			},
+		},
+		{
+			name:     "existing vars in env",
+			template: &rolloutsapi.AnalysisTemplate{},
+			exprCfg: &ArgumentEvaluationConfig{
+				Env: map[string]any{
+					"vars": map[string]string{
+						"test": "value1",
+					},
+				},
+				Vars: []ArgumentVariable{
+					{Name: "test", Value: "value2"},
+				},
+			},
+			assertions: func(t *testing.T, _ []rolloutsapi.Argument, err error) {
+				require.ErrorContains(t, err, "vars in argument evaluation config env is of unexpected type")
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/server/option/auth_test.go b/internal/server/option/auth_test.go
index 8958f3ec..130c4898 100644
--- a/internal/server/option/auth_test.go
+++ b/internal/server/option/auth_test.go
@@ -308,7 +308,8 @@ func TestAuthenticate(t *testing.T) {
 			authInterceptor: &authInterceptor{
 				cfg: config.ServerConfig{
 					OIDCConfig: &libOIDC.Config{
-						IssuerURL: testIDPIssuer,
+						IssuerURL:     testIDPIssuer,
+						UsernameClaim: "preferred_username",
 					},
 				},
 				parseUnverifiedJWTFn: func(_ string, claims jwt.Claims) (*jwt.Token, []string, error) {
@@ -322,8 +323,9 @@ func TestAuthenticate(t *testing.T) {
 					string,
 				) (claims, error) {
 					return claims{
-						"sub":   "ironman",
-						"email": "tony@starkindustries.com",
+						"preferred_username": "foo",
+						"sub":                "ironman",
+						"email":              "tony@starkindustries.com",
 						"groups": []string{
 							"avengers",
 							"shield",
@@ -345,6 +347,7 @@ func TestAuthenticate(t *testing.T) {
 				u, ok := user.InfoFromContext(ctx)
 				require.True(t, ok)
 				require.False(t, u.IsAdmin)
+				require.Equal(t, u.Username, "foo")
 				require.Equal(t, "ironman", u.Claims["sub"])
 				require.Equal(t, "tony@starkindustries.com", u.Claims["email"])
 				require.Equal(t, []string{"avengers", "shield"}, u.Claims["groups"])