Repository: akuity/kargo
Pull request #2289 (2024-07-14 05:12:33)

chore(deps): bump the go-patch group with 5 updates
Bumps the go-patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [github.com/aws/aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2) | `1.30.1` | `1.30.3` |
| [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) | `1.27.24` | `1.27.26` |
| [github.com/aws/aws-sdk-go-v2/credentials](https://github.com/aws/aws-sdk-go-v2) | `1.17.24` | `1.17.26` |
| [github.com/aws/aws-sdk-go-v2/service/ecr](https://github.com/aws/aws-sdk-go-v2) | `1.30.1` | `1.30.3` |
| [github.com/aws/aws-sdk-go-v2/service/sts](https://github.com/aws/aws-sdk-go-v2) | `1.30.1` | `1.30.3` |

Updates `github.com/aws/aws-sdk-go-v2` from 1.30.1 to 1.30.3
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0599931efcf551dc63f84ec669d7fb5cfe14f64c"><code>0599931</code></a> Release 2024-07-10.2</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/ca2a30ec8d823300ec948b784c491904eeba8929"><code>ca2a30e</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e2dff5f9bddaf1f23ea57e1e04d80f6ce2d54221"><code>e2dff5f</code></a> temporarily re-add jmespath dependency (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2712">#2712</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/383fd26928547348efed0ee1f8914d9e7a1b0287"><code>383fd26</code></a> Release 2024-07-10</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4a055f9d9e17eb7ef2206e8e37ba98d661d13e6a"><code>4a055f9</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e3457953351e6993f57f5ab8e373af8af70be45b"><code>e345795</code></a> Update endpoints model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/52a10ac239cc1aab2d070fbb004f6445ca0ddcc0"><code>52a10ac</code></a> Update API model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/adab0de9e4ae75aee4f894c99fd1e2ce2de0035e"><code>adab0de</code></a> remove unused jmespath dependency from main module (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2707">#2707</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0e07cc82b25692dce8f68e0b5bd0d0c5cdbcd279"><code>0e07cc8</code></a> Release 2024-07-09</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/5e3583451cd8a91dbca2cc22672c2cfa0c7860cf"><code>5e35834</code></a> Regenerated Clients</li>
<li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.30.3">compare view</a></li>
</ul>
</details>
<br />

Updates `github.com/aws/aws-sdk-go-v2/config` from 1.27.24 to 1.27.26
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0599931efcf551dc63f84ec669d7fb5cfe14f64c"><code>0599931</code></a> Release 2024-07-10.2</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/ca2a30ec8d823300ec948b784c491904eeba8929"><code>ca2a30e</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e2dff5f9bddaf1f23ea57e1e04d80f6ce2d54221"><code>e2dff5f</code></a> temporarily re-add jmespath dependency (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2712">#2712</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/383fd26928547348efed0ee1f8914d9e7a1b0287"><code>383fd26</code></a> Release 2024-07-10</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4a055f9d9e17eb7ef2206e8e37ba98d661d13e6a"><code>4a055f9</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e3457953351e6993f57f5ab8e373af8af70be45b"><code>e345795</code></a> Update endpoints model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/52a10ac239cc1aab2d070fbb004f6445ca0ddcc0"><code>52a10ac</code></a> Update API model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/adab0de9e4ae75aee4f894c99fd1e2ce2de0035e"><code>adab0de</code></a> remove unused jmespath dependency from main module (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2707">#2707</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0e07cc82b25692dce8f68e0b5bd0d0c5cdbcd279"><code>0e07cc8</code></a> Release 2024-07-09</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/5e3583451cd8a91dbca2cc22672c2cfa0c7860cf"><code>5e35834</code></a> Regenerated Clients</li>
<li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/config/v1.27.24...config/v1.27.26">compare view</a></li>
</ul>
</details>
<br />

Updates `github.com/aws/aws-sdk-go-v2/credentials` from 1.17.24 to 1.17.26
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0599931efcf551dc63f84ec669d7fb5cfe14f64c"><code>0599931</code></a> Release 2024-07-10.2</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/ca2a30ec8d823300ec948b784c491904eeba8929"><code>ca2a30e</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e2dff5f9bddaf1f23ea57e1e04d80f6ce2d54221"><code>e2dff5f</code></a> temporarily re-add jmespath dependency (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2712">#2712</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/383fd26928547348efed0ee1f8914d9e7a1b0287"><code>383fd26</code></a> Release 2024-07-10</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4a055f9d9e17eb7ef2206e8e37ba98d661d13e6a"><code>4a055f9</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e3457953351e6993f57f5ab8e373af8af70be45b"><code>e345795</code></a> Update endpoints model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/52a10ac239cc1aab2d070fbb004f6445ca0ddcc0"><code>52a10ac</code></a> Update API model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/adab0de9e4ae75aee4f894c99fd1e2ce2de0035e"><code>adab0de</code></a> remove unused jmespath dependency from main module (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2707">#2707</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0e07cc82b25692dce8f68e0b5bd0d0c5cdbcd279"><code>0e07cc8</code></a> Release 2024-07-09</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/5e3583451cd8a91dbca2cc22672c2cfa0c7860cf"><code>5e35834</code></a> Regenerated Clients</li>
<li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/credentials/v1.17.24...credentials/v1.17.26">compare view</a></li>
</ul>
</details>
<br />

Updates `github.com/aws/aws-sdk-go-v2/service/ecr` from 1.30.1 to 1.30.3
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0599931efcf551dc63f84ec669d7fb5cfe14f64c"><code>0599931</code></a> Release 2024-07-10.2</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/ca2a30ec8d823300ec948b784c491904eeba8929"><code>ca2a30e</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e2dff5f9bddaf1f23ea57e1e04d80f6ce2d54221"><code>e2dff5f</code></a> temporarily re-add jmespath dependency (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2712">#2712</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/383fd26928547348efed0ee1f8914d9e7a1b0287"><code>383fd26</code></a> Release 2024-07-10</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4a055f9d9e17eb7ef2206e8e37ba98d661d13e6a"><code>4a055f9</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e3457953351e6993f57f5ab8e373af8af70be45b"><code>e345795</code></a> Update endpoints model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/52a10ac239cc1aab2d070fbb004f6445ca0ddcc0"><code>52a10ac</code></a> Update API model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/adab0de9e4ae75aee4f894c99fd1e2ce2de0035e"><code>adab0de</code></a> remove unused jmespath dependency from main module (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2707">#2707</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0e07cc82b25692dce8f68e0b5bd0d0c5cdbcd279"><code>0e07cc8</code></a> Release 2024-07-09</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/5e3583451cd8a91dbca2cc22672c2cfa0c7860cf"><code>5e35834</code></a> Regenerated Clients</li>
<li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.30.3">compare view</a></li>
</ul>
</details>
<br />

Updates `github.com/aws/aws-sdk-go-v2/service/sts` from 1.30.1 to 1.30.3
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0599931efcf551dc63f84ec669d7fb5cfe14f64c"><code>0599931</code></a> Release 2024-07-10.2</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/ca2a30ec8d823300ec948b784c491904eeba8929"><code>ca2a30e</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e2dff5f9bddaf1f23ea57e1e04d80f6ce2d54221"><code>e2dff5f</code></a> temporarily re-add jmespath dependency (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2712">#2712</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/383fd26928547348efed0ee1f8914d9e7a1b0287"><code>383fd26</code></a> Release 2024-07-10</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/4a055f9d9e17eb7ef2206e8e37ba98d661d13e6a"><code>4a055f9</code></a> Regenerated Clients</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/e3457953351e6993f57f5ab8e373af8af70be45b"><code>e345795</code></a> Update endpoints model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/52a10ac239cc1aab2d070fbb004f6445ca0ddcc0"><code>52a10ac</code></a> Update API model</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/adab0de9e4ae75aee4f894c99fd1e2ce2de0035e"><code>adab0de</code></a> remove unused jmespath dependency from main module (<a href="https://redirect.github.com/aws/aws-sdk-go-v2/issues/2707">#2707</a>)</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/0e07cc82b25692dce8f68e0b5bd0d0c5cdbcd279"><code>0e07cc8</code></a> Release 2024-07-09</li>
<li><a href="https://github.com/aws/aws-sdk-go-v2/commit/5e3583451cd8a91dbca2cc22672c2cfa0c7860cf"><code>5e35834</code></a> Regenerated Clients</li>
<li>Additional commits viewable in <a href="https://github.com/aws/aws-sdk-go-v2/compare/v1.30.1...v1.30.3">compare view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions


</details>

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


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 3291c491a530e9ebc779f65d956b50384c46320f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/docs-kargo-akuity-io/deploys/66935e432d82780008addea3 |

Patch:
diff --git a/api/v1alpha1/freight_types.go b/api/v1alpha1/freight_types.go
index d143c1d1..704bb071 100644
--- a/api/v1alpha1/freight_types.go
+++ b/api/v1alpha1/freight_types.go
@@ -120,7 +120,7 @@ type GitCommit struct {
 	// resolved to this commit.
 	Tag string `json:"tag,omitempty" protobuf:"bytes,4,opt,name=tag"`
 	// HealthCheckCommit is the ID of a specific commit. When specified,
-	// assessments of Stage health will used this value (instead of ID) when
+	// assessments of Stage health will use this value (instead of ID) when
 	// determining if applicable sources of Argo CD Application resources
 	// associated with the Stage are or are not synced to this commit. Note that
 	// there are cases (as in that of Kargo Render being utilized as a promotion
@@ -136,6 +136,37 @@ type GitCommit struct {
 	Committer string `json:"committer,omitempty" protobuf:"bytes,8,opt,name=committer"`
 }
 
+// DeepEquals returns a bool indicating whether the receiver deep-equals the
+// provided GitCommit. I.e., all fields must be equal.
+func (g *GitCommit) DeepEquals(other *GitCommit) bool {
+	if g == nil && other == nil {
+		return true
+	}
+	if g == nil || other == nil {
+		return false
+	}
+	return g.RepoURL == other.RepoURL &&
+		g.ID == other.ID &&
+		g.Branch == other.Branch &&
+		g.Tag == other.Tag &&
+		g.HealthCheckCommit == other.HealthCheckCommit &&
+		g.Message == other.Message &&
+		g.Author == other.Author &&
+		g.Committer == other.Committer
+}
+
+// Equals returns a bool indicating whether two GitCommits are equivalent.
+func (g *GitCommit) Equals(rhs *GitCommit) bool {
+	if g == nil && rhs == nil {
+		return true
+	}
+	if (g == nil && rhs != nil) || (g != nil && rhs == nil) {
+		return false
+	}
+	// If we get to here, both operands are non-nil
+	return g.RepoURL == rhs.RepoURL && g.ID == rhs.ID
+}
+
 // FreightStatus describes a piece of Freight's most recently observed state.
 type FreightStatus struct {
 	// VerifiedIn describes the Stages in which this Freight has been verified
diff --git a/api/v1alpha1/generated.proto b/api/v1alpha1/generated.proto
index 1c9bbc86..e67dbf27 100644
--- a/api/v1alpha1/generated.proto
+++ b/api/v1alpha1/generated.proto
@@ -485,7 +485,7 @@ message GitCommit {
   optional string tag = 4;
 
   // HealthCheckCommit is the ID of a specific commit. When specified,
-  // assessments of Stage health will used this value (instead of ID) when
+  // assessments of Stage health will use this value (instead of ID) when
   // determining if applicable sources of Argo CD Application resources
   // associated with the Stage are or are not synced to this commit. Note that
   // there are cases (as in that of Kargo Render being utilized as a promotion
diff --git a/api/v1alpha1/stage_types.go b/api/v1alpha1/stage_types.go
index a2cb8bb1..c05e2d4f 100644
--- a/api/v1alpha1/stage_types.go
+++ b/api/v1alpha1/stage_types.go
@@ -613,6 +613,21 @@ type Image struct {
 	Digest string `json:"digest,omitempty" protobuf:"bytes,4,opt,name=digest"`
 }
 
+// DeepEquals returns a bool indicating whether the receiver deep-equals the
+// provided Image. I.e., all fields must be equal.
+func (i *Image) DeepEquals(other *Image) bool {
+	if i == nil && other == nil {
+		return true
+	}
+	if i == nil || other == nil {
+		return false
+	}
+	return i.RepoURL == other.RepoURL &&
+		i.GitRepoURL == other.GitRepoURL &&
+		i.Tag == other.Tag &&
+		i.Digest == other.Digest
+}
+
 // Chart describes a specific version of a Helm chart.
 type Chart struct {
 	// RepoURL specifies the URL of a Helm chart repository. Classic chart
@@ -628,16 +643,18 @@ type Chart struct {
 	Version string `json:"version,omitempty" protobuf:"bytes,3,opt,name=version"`
 }
 
-// Equals returns a bool indicating whether two GitCommits are equivalent.
-func (g *GitCommit) Equals(rhs *GitCommit) bool {
-	if g == nil && rhs == nil {
+// DeepEquals returns a bool indicating whether the receiver deep-equals the
+// provided Chart. I.e., all fields must be equal.
+func (c *Chart) DeepEquals(other *Chart) bool {
+	if c == nil && other == nil {
 		return true
 	}
-	if (g == nil && rhs != nil) || (g != nil && rhs == nil) {
+	if c == nil || other == nil {
 		return false
 	}
-	// If we get to here, both operands are non-nil
-	return g.RepoURL == rhs.RepoURL && g.ID == rhs.ID
+	return c.RepoURL == other.RepoURL &&
+		c.Name == other.Name &&
+		c.Version == other.Version
 }
 
 // Health describes the health of a Stage.
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_freights.yaml b/charts/kargo/resources/crds/kargo.akuity.io_freights.yaml
index 11d2f167..5526ab12 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_freights.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_freights.yaml
@@ -86,7 +86,7 @@ spec:
                 healthCheckCommit:
                   description: |-
                     HealthCheckCommit is the ID of a specific commit. When specified,
-                    assessments of Stage health will used this value (instead of ID) when
+                    assessments of Stage health will use this value (instead of ID) when
                     determining if applicable sources of Argo CD Application resources
                     associated with the Stage are or are not synced to this commit. Note that
                     there are cases (as in that of Kargo Render being utilized as a promotion
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml b/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
index 9e781d3e..4234489e 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_promotions.yaml
@@ -136,7 +136,7 @@ spec:
                         healthCheckCommit:
                           description: |-
                             HealthCheckCommit is the ID of a specific commit. When specified,
-                            assessments of Stage health will used this value (instead of ID) when
+                            assessments of Stage health will use this value (instead of ID) when
                             determining if applicable sources of Argo CD Application resources
                             associated with the Stage are or are not synced to this commit. Note that
                             there are cases (as in that of Kargo Render being utilized as a promotion
diff --git a/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml b/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
index a6ac7de5..7110827b 100644
--- a/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
+++ b/charts/kargo/resources/crds/kargo.akuity.io_stages.yaml
@@ -608,7 +608,7 @@ spec:
                         healthCheckCommit:
                           description: |-
                             HealthCheckCommit is the ID of a specific commit. When specified,
-                            assessments of Stage health will used this value (instead of ID) when
+                            assessments of Stage health will use this value (instead of ID) when
                             determining if applicable sources of Argo CD Application resources
                             associated with the Stage are or are not synced to this commit. Note that
                             there are cases (as in that of Kargo Render being utilized as a promotion
@@ -849,7 +849,7 @@ spec:
                             healthCheckCommit:
                               description: |-
                                 HealthCheckCommit is the ID of a specific commit. When specified,
-                                assessments of Stage health will used this value (instead of ID) when
+                                assessments of Stage health will use this value (instead of ID) when
                                 determining if applicable sources of Argo CD Application resources
                                 associated with the Stage are or are not synced to this commit. Note that
                                 there are cases (as in that of Kargo Render being utilized as a promotion
@@ -1096,7 +1096,7 @@ spec:
                                 healthCheckCommit:
                                   description: |-
                                     HealthCheckCommit is the ID of a specific commit. When specified,
-                                    assessments of Stage health will used this value (instead of ID) when
+                                    assessments of Stage health will use this value (instead of ID) when
                                     determining if applicable sources of Argo CD Application resources
                                     associated with the Stage are or are not synced to this commit. Note that
                                     there are cases (as in that of Kargo Render being utilized as a promotion
@@ -1424,7 +1424,7 @@ spec:
                           healthCheckCommit:
                             description: |-
                               HealthCheckCommit is the ID of a specific commit. When specified,
-                              assessments of Stage health will used this value (instead of ID) when
+                              assessments of Stage health will use this value (instead of ID) when
                               determining if applicable sources of Argo CD Application resources
                               associated with the Stage are or are not synced to this commit. Note that
                               there are cases (as in that of Kargo Render being utilized as a promotion
@@ -1672,7 +1672,7 @@ spec:
                             healthCheckCommit:
                               description: |-
                                 HealthCheckCommit is the ID of a specific commit. When specified,
-                                assessments of Stage health will used this value (instead of ID) when
+                                assessments of Stage health will use this value (instead of ID) when
                                 determining if applicable sources of Argo CD Application resources
                                 associated with the Stage are or are not synced to this commit. Note that
                                 there are cases (as in that of Kargo Render being utilized as a promotion
@@ -1919,7 +1919,7 @@ spec:
                                 healthCheckCommit:
                                   description: |-
                                     HealthCheckCommit is the ID of a specific commit. When specified,
-                                    assessments of Stage health will used this value (instead of ID) when
+                                    assessments of Stage health will use this value (instead of ID) when
                                     determining if applicable sources of Argo CD Application resources
                                     associated with the Stage are or are not synced to this commit. Note that
                                     there are cases (as in that of Kargo Render being utilized as a promotion
diff --git a/go.mod b/go.mod
index 06a446bb..8991dc13 100644
--- a/go.mod
+++ b/go.mod
@@ -10,11 +10,11 @@ require (
 	github.com/AlecAivazis/survey/v2 v2.3.7
 	github.com/Masterminds/semver/v3 v3.2.1
 	github.com/adrg/xdg v0.5.0
-	github.com/aws/aws-sdk-go-v2 v1.30.1
-	github.com/aws/aws-sdk-go-v2/config v1.27.24
-	github.com/aws/aws-sdk-go-v2/credentials v1.17.24
-	github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1
-	github.com/aws/aws-sdk-go-v2/service/sts v1.30.1
+	github.com/aws/aws-sdk-go-v2 v1.30.3
+	github.com/aws/aws-sdk-go-v2/config v1.27.26
+	github.com/aws/aws-sdk-go-v2/credentials v1.17.26
+	github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3
+	github.com/aws/aws-sdk-go-v2/service/sts v1.30.3
 	github.com/bacongobbler/browser v1.1.0
 	github.com/bombsimon/logrusr/v4 v4.1.0
 	github.com/coreos/go-oidc/v3 v3.11.0
@@ -61,14 +61,14 @@ require (
 require (
 	cloud.google.com/go/auth v0.7.0 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
-	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect
+	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
+	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 // indirect
-	github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect
-	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
+	github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 // indirect
+	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
 	github.com/aws/smithy-go v1.20.3 // indirect
 	github.com/google/go-github/v62 v62.0.0 // indirect
 	github.com/google/s2a-go v0.1.7 // indirect
diff --git a/go.sum b/go.sum
index 3de27091..ab2e22f3 100644
--- a/go.sum
+++ b/go.sum
@@ -31,32 +31,32 @@ github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
 github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o=
-github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
-github.com/aws/aws-sdk-go-v2/config v1.27.24 h1:NM9XicZ5o1CBU/MZaHwFtimRpWx9ohAUAqkG6AqSqPo=
-github.com/aws/aws-sdk-go-v2/config v1.27.24/go.mod h1:aXzi6QJTuQRVVusAO8/NxpdTeTyr/wRcybdDtfUwJSs=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.24 h1:YclAsrnb1/GTQNt2nzv+756Iw4mF8AOzcDfweWwwm/M=
-github.com/aws/aws-sdk-go-v2/credentials v1.17.24/go.mod h1:Hld7tmnAkoBQdTMNYZGzztzKRdA4fCdn9L83LOoigac=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9 h1:Aznqksmd6Rfv2HQN9cpqIV/lQRMaIpJkLLaJ1ZI76no=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.9/go.mod h1:WQr3MY7AxGNxaqAtsDWn+fBxmd4XvLkzeqQ8P1VM0/w=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8=
+github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
+github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
+github.com/aws/aws-sdk-go-v2/config v1.27.26 h1:T1kAefbKuNum/AbShMsZEro6eRkeOT8YILfE9wyjAYQ=
+github.com/aws/aws-sdk-go-v2/config v1.27.26/go.mod h1:ivWHkAWFrw/nxty5Fku7soTIVdqZaZ7dw+tc5iGW3GA=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.26 h1:tsm8g/nJxi8+/7XyJJcP2dLrnK/5rkFp6+i2nhmz5fk=
+github.com/aws/aws-sdk-go-v2/credentials v1.17.26/go.mod h1:3vAM49zkIa3q8WT6o9Ve5Z0vdByDMwmdScO0zvThTgI=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
-github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1 h1:zV3FlyuyPzfyFOXKu6mJW9JBGzdtOgpdlj3va+naOD8=
-github.com/aws/aws-sdk-go-v2/service/ecr v1.30.1/go.mod h1:l0zC7cSb2vAH1fr8+BRlolWT9cwlKpbRC8PjW6tyyIU=
+github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3 h1:+v2hv29pWaVDASIScHuUhDC93nqJGVlGf6cujrJMHZE=
+github.com/aws/aws-sdk-go-v2/service/ecr v1.30.3/go.mod h1:RhaP7Wil0+uuuhiE4FzOOEFZwkmFAk1ZflXzK+O3ptU=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15 h1:I9zMeF107l0rJrpnHpjEiiTSCKYAIw8mALiXcPsGBiA=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.15/go.mod h1:9xWJ3Q/S6Ojusz1UIkfycgD1mGirJfLLKqq3LPT7WN8=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 h1:p1GahKIjyMDZtiKoIn0/jAj/TkMzfzndDv5+zi2Mhgc=
-github.com/aws/aws-sdk-go-v2/service/sso v1.22.1/go.mod h1:/vWdhoIoYA5hYoPZ6fm7Sv4d8701PiG5VKe8/pPJL60=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 h1:+woJ607dllHJQtsnJLi52ycuqHMwlW+Wqm2Ppsfp4nQ=
-github.com/aws/aws-sdk-go-v2/service/sts v1.30.1/go.mod h1:jiNR3JqT15Dm+QWq2SRgh0x0bCNSRP2L25+CqPNpJlQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.3 h1:Fv1vD2L65Jnp5QRsdiM64JvUM4Xe+E0JyVsRQKv6IeA=
+github.com/aws/aws-sdk-go-v2/service/sso v1.22.3/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
+github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
 github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
 github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
 github.com/bacongobbler/browser v1.1.0 h1:6YTctUlzcApit1vpWgh+myjh8lQUyQRD2Ltoyvy2EoM=
diff --git a/internal/webhook/freight/webhook.go b/internal/webhook/freight/webhook.go
index af920e6c..57e0fdee 100644
--- a/internal/webhook/freight/webhook.go
+++ b/internal/webhook/freight/webhook.go
@@ -116,17 +116,20 @@ func newWebhook(
 
 func (w *webhook) Default(ctx context.Context, obj runtime.Object) error {
 	freight := obj.(*kargoapi.Freight) // nolint: forcetypeassert
-	// Re-calculate ID in case it wasn't set correctly to begin with -- possible
-	// if/when we allow users to create their own Freight.
-	freight.Name = freight.GenerateID()
 
-	// Sync the convenience alias field with the alias label
 	req, err := admission.RequestFromContext(ctx)
 	if err != nil {
 		return apierrors.NewInternalError(
 			fmt.Errorf("error getting admission request from context: %w", err),
 		)
 	}
+	if req.Operation == admissionv1.Create {
+		// Re-calculate ID in case it wasn't set correctly to begin with -- possible
+		// when users create their own Freight.
+		freight.Name = freight.GenerateID()
+	}
+
+	// Sync the convenience alias field with the alias label
 	if freight.Labels == nil {
 		freight.Labels = make(map[string]string, 1)
 	}
@@ -252,18 +255,16 @@ func (w *webhook) ValidateUpdate(
 		)
 	}
 
-	// Freight is meant to be immutable. We only need to compare the Name to a
-	// newly generated ID because these are both fingerprints that are
-	// deterministically derived from the artifacts referenced by the Freight.
-	if newFreight.Name != newFreight.GenerateID() || oldFreight.Warehouse != newFreight.Warehouse {
+	// Freight is meant to be immutable.
+	if changedPath, change, ok := compareFreight(oldFreight, newFreight); !ok {
 		return nil, apierrors.NewInvalid(
 			freightGroupKind,
 			oldFreight.Name,
 			field.ErrorList{
 				field.Invalid(
-					field.NewPath(""),
-					oldFreight,
-					"freight is immutable",
+					changedPath,
+					change,
+					"Freight is immutable",
 				),
 			},
 		)
@@ -501,3 +502,41 @@ func validateFreightArtifacts(
 
 	return nil
 }
+
+// compareFreight compares two Freight objects and returns the first field path
+// that differs between them, the new value, and a boolean indicating whether
+// the two Freight objects are equal.
+func compareFreight(old, new *kargoapi.Freight) (*field.Path, any, bool) {
+	if old.Warehouse != new.Warehouse {
+		return field.NewPath("warehouse"), new.Warehouse, false
+	}
+
+	if len(old.Commits) != len(new.Commits) {
+		return field.NewPath("commits"), new.Commits, false
+	}
+	for i, commit := range old.Commits {
+		if !commit.DeepEquals(&new.Commits[i]) {
+			return field.NewPath("commits").Index(i), new.Commits[i], false
+		}
+	}
+
+	if len(old.Images) != len(new.Images) {
+		return field.NewPath("images"), new.Images, false
+	}
+	for i, image := range old.Images {
+		if !image.DeepEquals(&new.Images[i]) {
+			return field.NewPath("images").Index(i), new.Images[i], false
+		}
+	}
+
+	if len(old.Charts) != len(new.Charts) {
+		return field.NewPath("charts"), new.Charts, false
+	}
+	for i, chart := range old.Charts {
+		if !chart.DeepEquals(&new.Charts[i]) {
+			return field.NewPath("charts").Index(i), new.Charts[i], false
+		}
+	}
+
+	return nil, nil, true
+}
diff --git a/ui/package.json b/ui/package.json
index 5e38b738..77934038 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -44,7 +44,7 @@
     "vite": "^5.3.3",
     "vite-plugin-compression": "^0.5.1",
     "vite-tsconfig-paths": "^4.3.2",
-    "vitest": "^1.6.0",
+    "vitest": "^2.0.2",
     "zx": "^8.1.4"
   },
   "packageManager": "pnpm@9.0.3",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 82d60d7b..fa1cace5 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -184,8 +184,8 @@ importers:
         specifier: ^4.3.2
         version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(less@4.2.0))
       vitest:
-        specifier: ^1.6.0
-        version: 1.6.0(@types/node@20.14.10)(less@4.2.0)
+        specifier: ^2.0.2
+        version: 2.0.2(@types/node@20.14.10)(less@4.2.0)
       zx:
         specifier: ^8.1.4
         version: 8.1.4
@@ -575,10 +575,6 @@ packages:
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
 
-  '@jest/schemas@29.6.3':
-    resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
   '@jridgewell/gen-mapping@0.3.5':
     resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
     engines: {node: '>=6.0.0'}
@@ -591,8 +587,8 @@ packages:
     resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
     engines: {node: '>=6.0.0'}
 
-  '@jridgewell/sourcemap-codec@1.4.15':
-    resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
 
   '@jridgewell/trace-mapping@0.3.25':
     resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
@@ -773,9 +769,6 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@sinclair/typebox@0.27.8':
-    resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
-
   '@tanstack/query-core@5.50.1':
     resolution: {integrity: sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==}
 
@@ -930,30 +923,29 @@ packages:
     peerDependencies:
       vite: ^4.2.0 || ^5.0.0
 
-  '@vitest/expect@1.6.0':
-    resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
+  '@vitest/expect@2.0.2':
+    resolution: {integrity: sha512-nKAvxBYqcDugYZ4nJvnm5OR8eDJdgWjk4XM9owQKUjzW70q0icGV2HVnQOyYsp906xJaBDUXw0+9EHw2T8e0mQ==}
+
+  '@vitest/pretty-format@2.0.2':
+    resolution: {integrity: sha512-SBCyOXfGVvddRd9r2PwoVR0fonQjh9BMIcBMlSzbcNwFfGr6ZhOhvBzurjvi2F4ryut2HcqiFhNeDVGwru8tLg==}
 
-  '@vitest/runner@1.6.0':
-    resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
+  '@vitest/runner@2.0.2':
+    resolution: {integrity: sha512-OCh437Vi8Wdbif1e0OvQcbfM3sW4s2lpmOjAE7qfLrpzJX2M7J1IQlNvEcb/fu6kaIB9n9n35wS0G2Q3en5kHg==}
 
-  '@vitest/snapshot@1.6.0':
-    resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==}
+  '@vitest/snapshot@2.0.2':
+    resolution: {integrity: sha512-Yc2ewhhZhx+0f9cSUdfzPRcsM6PhIb+S43wxE7OG0kTxqgqzo8tHkXFuFlndXeDMp09G3sY/X5OAo/RfYydf1g==}
 
-  '@vitest/spy@1.6.0':
-    resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
+  '@vitest/spy@2.0.2':
+    resolution: {integrity: sha512-MgwJ4AZtCgqyp2d7WcQVE8aNG5vQ9zu9qMPYQHjsld/QVsrvg78beNrXdO4HYkP0lDahCO3P4F27aagIag+SGQ==}
 
-  '@vitest/utils@1.6.0':
-    resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
+  '@vitest/utils@2.0.2':
+    resolution: {integrity: sha512-pxCY1v7kmOCWYWjzc0zfjGTA3Wmn8PKnlPvSrsA643P1NHl1fOyXj2Q9SaNlrlFE+ivCsxM80Ov3AR82RmHCWQ==}
 
   acorn-jsx@5.3.2:
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
       acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
 
-  acorn-walk@8.3.3:
-    resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==}
-    engines: {node: '>=0.4.0'}
-
   acorn@8.12.0:
     resolution: {integrity: sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==}
     engines: {node: '>=0.4.0'}
@@ -978,10 +970,6 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
-  ansi-styles@5.2.0:
-    resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
-    engines: {node: '>=10'}
-
   ansi-styles@6.2.1:
     resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
     engines: {node: '>=12'}
@@ -1058,8 +1046,9 @@ packages:
     resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
     engines: {node: '>= 0.4'}
 
-  assertion-error@1.1.0:
-    resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
+  assertion-error@2.0.1:
+    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+    engines: {node: '>=12'}
 
   autoprefixer@10.4.19:
     resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
@@ -1131,9 +1120,9 @@ packages:
   caniuse-lite@1.0.30001640:
     resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==}
 
-  chai@4.4.1:
-    resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==}
-    engines: {node: '>=4'}
+  chai@5.1.1:
+    resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
+    engines: {node: '>=12'}
 
   chalk@2.4.2:
     resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
@@ -1143,8 +1132,9 @@ packages:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
-  check-error@1.0.3:
-    resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+  check-error@2.1.1:
+    resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+    engines: {node: '>= 16'}
 
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
@@ -1194,9 +1184,6 @@ packages:
   concat-map@0.0.1:
     resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
 
-  confbox@0.1.7:
-    resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
-
   convert-source-map@2.0.0:
     resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
 
@@ -1355,8 +1342,8 @@ packages:
   decimal.js-light@2.5.1:
     resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
 
-  deep-eql@4.1.4:
-    resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
+  deep-eql@5.0.2:
+    resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
     engines: {node: '>=6'}
 
   deep-is@0.1.4:
@@ -1373,10 +1360,6 @@ packages:
   didyoumean@1.2.2:
     resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
 
-  diff-sequences@29.6.3:
-    resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
   dir-glob@3.0.1:
     resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
     engines: {node: '>=8'}
@@ -1960,9 +1943,6 @@ packages:
   js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
 
-  js-tokens@9.0.0:
-    resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==}
-
   js-yaml@4.1.0:
     resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
     hasBin: true
@@ -2033,10 +2013,6 @@ packages:
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
-  local-pkg@0.5.0:
-    resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
-    engines: {node: '>=14'}
-
   locate-path@6.0.0:
     resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
     engines: {node: '>=10'}
@@ -2057,8 +2033,8 @@ packages:
     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
     hasBin: true
 
-  loupe@2.3.7:
-    resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+  loupe@3.1.1:
+    resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
 
   lru-cache@10.3.0:
     resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==}
@@ -2124,9 +2100,6 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  mlly@1.7.1:
-    resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==}
-
   moment@2.30.1:
     resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
 
@@ -2262,10 +2235,6 @@ packages:
     resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
     engines: {node: '>=10'}
 
-  p-limit@5.0.0:
-    resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
-    engines: {node: '>=18'}
-
   p-locate@5.0.0:
     resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
     engines: {node: '>=10'}
@@ -2310,8 +2279,9 @@ packages:
   pathe@1.1.2:
     resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
 
-  pathval@1.1.1:
-    resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
+  pathval@2.0.0:
+    resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
+    engines: {node: '>= 14.16'}
 
   picocolors@1.0.1:
     resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@@ -2332,9 +2302,6 @@ packages:
     resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
     engines: {node: '>= 6'}
 
-  pkg-types@1.1.1:
-    resolution: {integrity: sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==}
-
   possible-typed-array-names@1.0.0:
     resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
     engines: {node: '>= 0.4'}
@@ -2560,10 +2527,6 @@ packages:
     engines: {node: '>=14'}
     hasBin: true
 
-  pretty-format@29.7.0:
-    resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-
   prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
 
@@ -3062,9 +3025,6 @@ packages:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
 
-  strip-literal@2.1.0:
-    resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==}
-
   stylehacks@7.0.2:
     resolution: {integrity: sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==}
     engines: {node: ^18.12.0 || ^20.9.0 || >=22.0}
@@ -3128,12 +3088,16 @@ packages:
   tinybench@2.8.0:
     resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
 
-  tinypool@0.8.4:
-    resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+  tinypool@1.0.0:
+    resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+
+  tinyrainbow@1.2.0:
+    resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
     engines: {node: '>=14.0.0'}
 
-  tinyspy@2.2.1:
-    resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+  tinyspy@3.0.0:
+    resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==}
     engines: {node: '>=14.0.0'}
 
   to-fast-properties@2.0.0:
@@ -3179,10 +3143,6 @@ packages:
     resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
     engines: {node: '>= 0.8.0'}
 
-  type-detect@4.0.8:
-    resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
-    engines: {node: '>=4'}
-
   typed-array-buffer@1.0.0:
     resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
     engines: {node: '>= 0.4'}
@@ -3219,9 +3179,6 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
-  ufo@1.5.3:
-    resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
-
   unbox-primitive@1.0.2:
     resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
 
@@ -3257,8 +3214,8 @@ packages:
   victory-vendor@36.9.2:
     resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
 
-  vite-node@1.6.0:
-    resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
+  vite-node@2.0.2:
+    resolution: {integrity: sha512-w4vkSz1Wo+NIQg8pjlEn0jQbcM/0D+xVaYjhw3cvarTanLLBh54oNiRbsT8PNK5GfuST0IlVXjsNRoNlqvY/fw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
 
@@ -3308,15 +3265,15 @@ packages:
       terser:
         optional: true
 
-  vitest@1.6.0:
-    resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
+  vitest@2.0.2:
+    resolution: {integrity: sha512-WlpZ9neRIjNBIOQwBYfBSr0+of5ZCbxT2TVGKW4Lv0c8+srCFIiRdsP7U009t8mMn821HQ4XKgkx5dVWpyoyLw==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
       '@edge-runtime/vm': '*'
       '@types/node': ^18.0.0 || >=20.0.0
-      '@vitest/browser': 1.6.0
-      '@vitest/ui': 1.6.0
+      '@vitest/browser': 2.0.2
+      '@vitest/ui': 2.0.2
       happy-dom: '*'
       jsdom: '*'
     peerDependenciesMeta:
@@ -3373,8 +3330,8 @@ packages:
     engines: {node: '>= 8'}
     hasBin: true
 
-  why-is-node-running@2.2.2:
-    resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
+  why-is-node-running@2.3.0:
+    resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
     engines: {node: '>=8'}
     hasBin: true
 
@@ -3414,10 +3371,6 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
-  yocto-queue@1.0.0:
-    resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==}
-    engines: {node: '>=12.20'}
-
   zod@3.23.8:
     resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
 
@@ -3790,26 +3743,22 @@ snapshots:
       wrap-ansi: 8.1.0
       wrap-ansi-cjs: wrap-ansi@7.0.0
 
-  '@jest/schemas@29.6.3':
-    dependencies:
-      '@sinclair/typebox': 0.27.8
-
   '@jridgewell/gen-mapping@0.3.5':
     dependencies:
       '@jridgewell/set-array': 1.2.1
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
       '@jridgewell/trace-mapping': 0.3.25
 
   '@jridgewell/resolve-uri@3.1.2': {}
 
   '@jridgewell/set-array@1.2.1': {}
 
-  '@jridgewell/sourcemap-codec@1.4.15': {}
+  '@jridgewell/sourcemap-codec@1.5.0': {}
 
   '@jridgewell/trace-mapping@0.3.25':
     dependencies:
       '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
 
   '@monaco-editor/loader@1.4.0(monaco-editor@0.50.0)':
     dependencies:
@@ -3969,8 +3918,6 @@ snapshots:
   '@rollup/rollup-win32-x64-msvc@4.18.1':
     optional: true
 
-  '@sinclair/typebox@0.27.8': {}
-
   '@tanstack/query-core@5.50.1': {}
 
   '@tanstack/react-query@5.50.1(react@18.3.1)':
@@ -4164,43 +4111,43 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@vitest/expect@1.6.0':
+  '@vitest/expect@2.0.2':
+    dependencies:
+      '@vitest/spy': 2.0.2
+      '@vitest/utils': 2.0.2
+      chai: 5.1.1
+      tinyrainbow: 1.2.0
+
+  '@vitest/pretty-format@2.0.2':
     dependencies:
-      '@vitest/spy': 1.6.0
-      '@vitest/utils': 1.6.0
-      chai: 4.4.1
+      tinyrainbow: 1.2.0
 
-  '@vitest/runner@1.6.0':
+  '@vitest/runner@2.0.2':
     dependencies:
-      '@vitest/utils': 1.6.0
-      p-limit: 5.0.0
+      '@vitest/utils': 2.0.2
       pathe: 1.1.2
 
-  '@vitest/snapshot@1.6.0':
+  '@vitest/snapshot@2.0.2':
     dependencies:
+      '@vitest/pretty-format': 2.0.2
       magic-string: 0.30.10
       pathe: 1.1.2
-      pretty-format: 29.7.0
 
-  '@vitest/spy@1.6.0':
+  '@vitest/spy@2.0.2':
     dependencies:
-      tinyspy: 2.2.1
+      tinyspy: 3.0.0
 
-  '@vitest/utils@1.6.0':
+  '@vitest/utils@2.0.2':
     dependencies:
-      diff-sequences: 29.6.3
+      '@vitest/pretty-format': 2.0.2
       estree-walker: 3.0.3
-      loupe: 2.3.7
-      pretty-format: 29.7.0
+      loupe: 3.1.1
+      tinyrainbow: 1.2.0
 
   acorn-jsx@5.3.2(acorn@8.12.0):
     dependencies:
       acorn: 8.12.0
 
-  acorn-walk@8.3.3:
-    dependencies:
-      acorn: 8.12.0
-
   acorn@8.12.0: {}
 
   ajv@6.12.6:
@@ -4222,8 +4169,6 @@ snapshots:
     dependencies:
       color-convert: 2.0.1
 
-  ansi-styles@5.2.0: {}
-
   ansi-styles@6.2.1: {}
 
   antd@5.19.1(date-fns@3.6.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
@@ -4392,7 +4337,7 @@ snapshots:
       is-array-buffer: 3.0.4
       is-shared-array-buffer: 1.0.3
 
-  assertion-error@1.1.0: {}
+  assertion-error@2.0.1: {}
 
   autoprefixer@10.4.19(postcss@8.4.39):
     dependencies:
@@ -4468,15 +4413,13 @@ snapshots:
 
   caniuse-lite@1.0.30001640: {}
 
-  chai@4.4.1:
+  chai@5.1.1:
     dependencies:
-      assertion-error: 1.1.0
-      check-error: 1.0.3
-      deep-eql: 4.1.4
-      get-func-name: 2.0.2
-      loupe: 2.3.7
-      pathval: 1.1.1
-      type-detect: 4.0.8
+      assertion-error: 2.0.1
+      check-error: 2.1.1
+      deep-eql: 5.0.2
+      loupe: 3.1.1
+      pathval: 2.0.0
 
   chalk@2.4.2:
     dependencies:
@@ -4489,9 +4432,7 @@ snapshots:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
-  check-error@1.0.3:
-    dependencies:
-      get-func-name: 2.0.2
+  check-error@2.1.1: {}
 
   chokidar@3.6.0:
     dependencies:
@@ -4539,8 +4480,6 @@ snapshots:
 
   concat-map@0.0.1: {}
 
-  confbox@0.1.7: {}
-
   convert-source-map@2.0.0: {}
 
   copy-anything@2.0.6:
@@ -4712,9 +4651,7 @@ snapshots:
 
   decimal.js-light@2.5.1: {}
 
-  deep-eql@4.1.4:
-    dependencies:
-      type-detect: 4.0.8
+  deep-eql@5.0.2: {}
 
   deep-is@0.1.4: {}
 
@@ -4732,8 +4669,6 @@ snapshots:
 
   didyoumean@1.2.2: {}
 
-  diff-sequences@29.6.3: {}
-
   dir-glob@3.0.1:
     dependencies:
       path-type: 4.0.0
@@ -5468,8 +5403,6 @@ snapshots:
 
   js-tokens@4.0.0: {}
 
-  js-tokens@9.0.0: {}
-
   js-yaml@4.1.0:
     dependencies:
       argparse: 2.0.1
@@ -5545,11 +5478,6 @@ snapshots:
 
   lines-and-columns@1.2.4: {}
 
-  local-pkg@0.5.0:
-    dependencies:
-      mlly: 1.7.1
-      pkg-types: 1.1.1
-
   locate-path@6.0.0:
     dependencies:
       p-locate: 5.0.0
@@ -5566,7 +5494,7 @@ snapshots:
     dependencies:
       js-tokens: 4.0.0
 
-  loupe@2.3.7:
+  loupe@3.1.1:
     dependencies:
       get-func-name: 2.0.2
 
@@ -5578,7 +5506,7 @@ snapshots:
 
   magic-string@0.30.10:
     dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
 
   make-dir@2.1.0:
     dependencies:
@@ -5620,13 +5548,6 @@ snapshots:
 
   mkdirp@3.0.1: {}
 
-  mlly@1.7.1:
-    dependencies:
-      acorn: 8.12.0
-      pathe: 1.1.2
-      pkg-types: 1.1.1
-      ufo: 1.5.3
-
   moment@2.30.1: {}
 
   monaco-editor@0.50.0: {}
@@ -5785,10 +5706,6 @@ snapshots:
     dependencies:
       yocto-queue: 0.1.0
 
-  p-limit@5.0.0:
-    dependencies:
-      yocto-queue: 1.0.0
-
   p-locate@5.0.0:
     dependencies:
       p-limit: 3.1.0
@@ -5820,7 +5737,7 @@ snapshots:
 
   pathe@1.1.2: {}
 
-  pathval@1.1.1: {}
+  pathval@2.0.0: {}
 
   picocolors@1.0.1: {}
 
@@ -5833,12 +5750,6 @@ snapshots:
 
   pirates@4.0.6: {}
 
-  pkg-types@1.1.1:
-    dependencies:
-      confbox: 0.1.7
-      mlly: 1.7.1
-      pathe: 1.1.2
-
   possible-typed-array-names@1.0.0: {}
 
   postcss-calc@10.0.0(postcss@8.4.39):
@@ -6037,12 +5948,6 @@ snapshots:
 
   prettier@3.3.2: {}
 
-  pretty-format@29.7.0:
-    dependencies:
-      '@jest/schemas': 29.6.3
-      ansi-styles: 5.2.0
-      react-is: 18.3.1
-
   prop-types@15.8.1:
     dependencies:
       loose-envify: 1.4.0
@@ -6689,10 +6594,6 @@ snapshots:
 
   strip-json-comments@3.1.1: {}
 
-  strip-literal@2.1.0:
-    dependencies:
-      js-tokens: 9.0.0
-
   stylehacks@7.0.2(postcss@8.4.39):
     dependencies:
       browserslist: 4.23.1
@@ -6784,9 +6685,11 @@ snapshots:
 
   tinybench@2.8.0: {}
 
-  tinypool@0.8.4: {}
+  tinypool@1.0.0: {}
+
+  tinyrainbow@1.2.0: {}
 
-  tinyspy@2.2.1: {}
+  tinyspy@3.0.0: {}
 
   to-fast-properties@2.0.0: {}
 
@@ -6824,8 +6727,6 @@ snapshots:
     dependencies:
       prelude-ls: 1.2.1
 
-  type-detect@4.0.8: {}
-
   typed-array-buffer@1.0.0:
     dependencies:
       call-bind: 1.0.7
@@ -6887,8 +6788,6 @@ snapshots:
 
   typescript@5.5.3: {}
 
-  ufo@1.5.3: {}
-
   unbox-primitive@1.0.2:
     dependencies:
       call-bind: 1.0.7
@@ -6939,12 +6838,12 @@ snapshots:
       d3-time: 3.1.0
       d3-timer: 3.0.1
 
-  vite-node@1.6.0(@types/node@20.14.10)(less@4.2.0):
+  vite-node@2.0.2(@types/node@20.14.10)(less@4.2.0):
     dependencies:
       cac: 6.7.14
       debug: 4.3.5
       pathe: 1.1.2
-      picocolors: 1.0.1
+      tinyrainbow: 1.2.0
       vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)
     transitivePeerDependencies:
       - '@types/node'
@@ -6990,28 +6889,27 @@ snapshots:
       fsevents: 2.3.3
       less: 4.2.0
 
-  vitest@1.6.0(@types/node@20.14.10)(less@4.2.0):
+  vitest@2.0.2(@types/node@20.14.10)(less@4.2.0):
     dependencies:
-      '@vitest/expect': 1.6.0
-      '@vitest/runner': 1.6.0
-      '@vitest/snapshot': 1.6.0
-      '@vitest/spy': 1.6.0
-      '@vitest/utils': 1.6.0
-      acorn-walk: 8.3.3
-      chai: 4.4.1
+      '@ampproject/remapping': 2.3.0
+      '@vitest/expect': 2.0.2
+      '@vitest/pretty-format': 2.0.2
+      '@vitest/runner': 2.0.2
+      '@vitest/snapshot': 2.0.2
+      '@vitest/spy': 2.0.2
+      '@vitest/utils': 2.0.2
+      chai: 5.1.1
       debug: 4.3.5
       execa: 8.0.1
-      local-pkg: 0.5.0
       magic-string: 0.30.10
       pathe: 1.1.2
-      picocolors: 1.0.1
       std-env: 3.7.0
-      strip-literal: 2.1.0
       tinybench: 2.8.0
-      tinypool: 0.8.4
+      tinypool: 1.0.0
+      tinyrainbow: 1.2.0
       vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)
-      vite-node: 1.6.0(@types/node@20.14.10)(less@4.2.0)
-      why-is-node-running: 2.2.2
+      vite-node: 2.0.2(@types/node@20.14.10)(less@4.2.0)
+      why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/node': 20.14.10
     transitivePeerDependencies:
@@ -7086,7 +6984,7 @@ snapshots:
     dependencies:
       isexe: 2.0.0
 
-  why-is-node-running@2.2.2:
+  why-is-node-running@2.3.0:
     dependencies:
       siginfo: 2.0.0
       stackback: 0.0.2
@@ -7125,8 +7023,6 @@ snapshots:
 
   yocto-queue@0.1.0: {}
 
-  yocto-queue@1.0.0: {}
-
   zod@3.23.8: {}
 
   zx@8.1.4:
diff --git a/ui/src/gen/schema/freights.kargo.akuity.io_v1alpha1.json b/ui/src/gen/schema/freights.kargo.akuity.io_v1alpha1.json
index 17f8cf84..cb162ed8 100644
--- a/ui/src/gen/schema/freights.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/freights.kargo.akuity.io_v1alpha1.json
@@ -50,7 +50,7 @@
             "type": "string"
           },
           "healthCheckCommit": {
-            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
             "type": "string"
           },
           "id": {
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 f8e8136c..e833419a 100644
--- a/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/promotions.kargo.akuity.io_v1alpha1.json
@@ -85,7 +85,7 @@
                     "type": "string"
                   },
                   "healthCheckCommit": {
-                    "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                    "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                     "type": "string"
                   },
                   "id": {
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 3c4d2c9a..36472a68 100644
--- a/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
+++ b/ui/src/gen/schema/stages.kargo.akuity.io_v1alpha1.json
@@ -495,7 +495,7 @@
                     "type": "string"
                   },
                   "healthCheckCommit": {
-                    "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                    "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                     "type": "string"
                   },
                   "id": {
@@ -721,7 +721,7 @@
                         "type": "string"
                       },
                       "healthCheckCommit": {
-                        "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                        "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                         "type": "string"
                       },
                       "id": {
@@ -951,7 +951,7 @@
                             "type": "string"
                           },
                           "healthCheckCommit": {
-                            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                             "type": "string"
                           },
                           "id": {
@@ -1275,7 +1275,7 @@
                       "type": "string"
                     },
                     "healthCheckCommit": {
-                      "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                      "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                       "type": "string"
                     },
                     "id": {
@@ -1507,7 +1507,7 @@
                         "type": "string"
                       },
                       "healthCheckCommit": {
-                        "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                        "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                         "type": "string"
                       },
                       "id": {
@@ -1737,7 +1737,7 @@
                             "type": "string"
                           },
                           "healthCheckCommit": {
-                            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will used this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
+                            "description": "HealthCheckCommit is the ID of a specific commit. When specified,\nassessments of Stage health will use this value (instead of ID) when\ndetermining if applicable sources of Argo CD Application resources\nassociated with the Stage are or are not synced to this commit. Note that\nthere are cases (as in that of Kargo Render being utilized as a promotion\nmechanism) wherein the value of this field may differ from the commit ID\nfound in the ID field.",
                             "type": "string"
                           },
                           "id": {
diff --git a/ui/src/gen/v1alpha1/generated_pb.ts b/ui/src/gen/v1alpha1/generated_pb.ts
index bc6c2d52..dfe92c0b 100644
--- a/ui/src/gen/v1alpha1/generated_pb.ts
+++ b/ui/src/gen/v1alpha1/generated_pb.ts
@@ -1589,7 +1589,7 @@ export class GitCommit extends Message<GitCommit> {
 
   /**
    * HealthCheckCommit is the ID of a specific commit. When specified,
-   * assessments of Stage health will used this value (instead of ID) when
+   * assessments of Stage health will use this value (instead of ID) when
    * determining if applicable sources of Argo CD Application resources
    * associated with the Stage are or are not synced to this commit. Note that
    * there are cases (as in that of Kargo Render being utilized as a promotion


Test patch:
diff --git a/api/v1alpha1/freight_types_test.go b/api/v1alpha1/freight_types_test.go
index 2901e761..a71c94d5 100644
--- a/api/v1alpha1/freight_types_test.go
+++ b/api/v1alpha1/freight_types_test.go
@@ -6,6 +6,167 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+func TestGitCommitDeepEquals(t *testing.T) {
+	testCases := []struct {
+		name           string
+		a              *GitCommit
+		b              *GitCommit
+		expectedResult bool
+	}{
+		{
+			name:           "a and b both nil",
+			expectedResult: true,
+		},
+		{
+			name:           "only a is nil",
+			b:              &GitCommit{},
+			expectedResult: false,
+		},
+		{
+			name:           "only b is nil",
+			a:              &GitCommit{},
+			expectedResult: false,
+		},
+		{
+			name: "repoURLs differ",
+			a: &GitCommit{
+				RepoURL: "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "commit IDs differ",
+			a: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "branch names differ",
+			a: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Branch:  "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Branch:  "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "tags differ",
+			a: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Tag:     "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Tag:     "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "health check commits differ",
+			a: &GitCommit{
+				RepoURL:           "fake-url",
+				ID:                "fake-commit-id",
+				HealthCheckCommit: "foo",
+			},
+			b: &GitCommit{
+				RepoURL:           "fake-url",
+				ID:                "fake-commit-id",
+				HealthCheckCommit: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "messages differ",
+			a: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Message: "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Message: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "authors differ",
+			a: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Author:  "foo",
+			},
+			b: &GitCommit{
+				RepoURL: "fake-url",
+				ID:      "fake-commit-id",
+				Author:  "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "committers differ",
+			a: &GitCommit{
+				RepoURL:   "fake-url",
+				ID:        "fake-commit-id",
+				Committer: "foo",
+			},
+			b: &GitCommit{
+				RepoURL:   "fake-url",
+				ID:        "fake-commit-id",
+				Committer: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "perfect match",
+			a: &GitCommit{
+				RepoURL:           "fake-url",
+				ID:                "fake-commit-id",
+				Branch:            "fake-branch",
+				Tag:               "fake-tag",
+				HealthCheckCommit: "fake-health-id",
+				Message:           "fake-message",
+				Author:            "fake-author",
+				Committer:         "fake-committer",
+			},
+			b: &GitCommit{
+				RepoURL:           "fake-url",
+				ID:                "fake-commit-id",
+				Branch:            "fake-branch",
+				Tag:               "fake-tag",
+				HealthCheckCommit: "fake-health-id",
+				Message:           "fake-message",
+				Author:            "fake-author",
+				Committer:         "fake-committer",
+			},
+			expectedResult: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			require.Equal(t, testCase.expectedResult, testCase.a.DeepEquals(testCase.b))
+			require.Equal(t, testCase.expectedResult, testCase.b.DeepEquals(testCase.a))
+		})
+	}
+}
+
 func TestGitCommitEquals(t *testing.T) {
 	testCases := []struct {
 		name           string
diff --git a/api/v1alpha1/stage_types_test.go b/api/v1alpha1/stage_types_test.go
index ea4612d8..7bbae43e 100644
--- a/api/v1alpha1/stage_types_test.go
+++ b/api/v1alpha1/stage_types_test.go
@@ -224,3 +224,177 @@ func TestVerificationInfoStack_UpdateOrPush(t *testing.T) {
 		})
 	}
 }
+
+func TestImageDeepEquals(t *testing.T) {
+	testCases := []struct {
+		name           string
+		a              *Image
+		b              *Image
+		expectedResult bool
+	}{
+		{
+			name:           "a and b both nil",
+			expectedResult: true,
+		},
+		{
+			name:           "only a is nil",
+			b:              &Image{},
+			expectedResult: false,
+		},
+		{
+			name:           "only b is nil",
+			a:              &Image{},
+			expectedResult: false,
+		},
+		{
+			name: "repo URLs differ",
+			a: &Image{
+				RepoURL: "foo",
+			},
+			b: &Image{
+				RepoURL: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "git repo URLs differ",
+			a: &Image{
+				RepoURL:    "fake-url",
+				GitRepoURL: "foo",
+			},
+			b: &Image{
+				RepoURL:    "fake-url",
+				GitRepoURL: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "image tags differ",
+			a: &Image{
+				RepoURL: "fake-url",
+				Tag:     "foo",
+			},
+			b: &Image{
+				RepoURL: "fake-url",
+				Tag:     "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "image digests differ",
+			a: &Image{
+				RepoURL: "fake-url",
+				Digest:  "foo",
+			},
+			b: &Image{
+				RepoURL: "fake-url",
+				Digest:  "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "perfect match",
+			a: &Image{
+				RepoURL:    "fake-url",
+				GitRepoURL: "fake-repo-url",
+				Tag:        "fake-tag",
+				Digest:     "fake-digest",
+			},
+			b: &Image{
+				RepoURL:    "fake-url",
+				GitRepoURL: "fake-repo-url",
+				Tag:        "fake-tag",
+				Digest:     "fake-digest",
+			},
+			expectedResult: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			require.Equal(t, testCase.expectedResult, testCase.a.DeepEquals(testCase.b))
+			require.Equal(t, testCase.expectedResult, testCase.b.DeepEquals(testCase.a))
+		})
+	}
+}
+
+func TestChartDeepEquals(t *testing.T) {
+	testCases := []struct {
+		name           string
+		a              *Chart
+		b              *Chart
+		expectedResult bool
+	}{
+		{
+			name:           "a and b both nil",
+			expectedResult: true,
+		},
+		{
+			name:           "only a is nil",
+			b:              &Chart{},
+			expectedResult: false,
+		},
+		{
+			name:           "only b is nil",
+			a:              &Chart{},
+			expectedResult: false,
+		},
+		{
+			name: "repo URLs differ",
+			a: &Chart{
+				RepoURL: "foo",
+			},
+			b: &Chart{
+				RepoURL: "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "chart names differ",
+			a: &Chart{
+				RepoURL: "fake-url",
+				Name:    "foo",
+			},
+			b: &Chart{
+				RepoURL: "fake-url",
+				Name:    "bar",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "chart versions differ",
+			a: &Chart{
+				RepoURL: "fake-url",
+				Name:    "fake-name",
+				Version: "v1.0.0",
+			},
+			b: &Chart{
+				RepoURL: "fake-url",
+				Name:    "fake-name",
+				Version: "v2.0.0",
+			},
+			expectedResult: false,
+		},
+		{
+			name: "perfect match",
+			a: &Chart{
+				RepoURL: "fake-url",
+				Name:    "fake-name",
+				Version: "v1.0.0",
+			},
+			b: &Chart{
+				RepoURL: "fake-url",
+				Name:    "fake-name",
+				Version: "v1.0.0",
+			},
+			expectedResult: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			require.Equal(t, testCase.expectedResult, testCase.a.DeepEquals(testCase.b))
+			require.Equal(t, testCase.expectedResult, testCase.b.DeepEquals(testCase.a))
+		})
+	}
+}
diff --git a/internal/webhook/freight/webhook_test.go b/internal/webhook/freight/webhook_test.go
index 3d095035..bfbcb9c4 100644
--- a/internal/webhook/freight/webhook_test.go
+++ b/internal/webhook/freight/webhook_test.go
@@ -16,6 +16,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/validation/field"
 	"k8s.io/apiserver/pkg/authentication/serviceaccount"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -105,6 +106,18 @@ func TestDefault(t *testing.T) {
 				require.Equal(t, "fake-alias", freight.Labels[kargoapi.AliasLabelKey])
 			},
 		},
+		{
+			name:    "create with empty name",
+			op:      admissionv1.Create,
+			webhook: &webhook{},
+			freight: &kargoapi.Freight{
+				Alias: "fake-alias",
+			},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, err error) {
+				require.NoError(t, err)
+				require.NotEmpty(t, freight.Name)
+			},
+		},
 		{
 			name:    "update with empty alias",
 			op:      admissionv1.Update,
@@ -118,7 +131,6 @@ func TestDefault(t *testing.T) {
 			},
 			assertions: func(t *testing.T, freight *kargoapi.Freight, err error) {
 				require.NoError(t, err)
-				require.NotEmpty(t, freight.Name)
 				require.Empty(t, freight.Alias)
 				_, ok := freight.Labels[kargoapi.AliasLabelKey]
 				require.False(t, ok)
@@ -532,7 +544,7 @@ func TestValidateUpdate(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ *fakeevent.EventRecorder, err error) {
 				require.ErrorContains(t, err, "is invalid")
-				require.ErrorContains(t, err, "freight is immutable")
+				require.ErrorContains(t, err, "Freight is immutable")
 			},
 		},
 
@@ -561,7 +573,7 @@ func TestValidateUpdate(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ *fakeevent.EventRecorder, err error) {
 				require.ErrorContains(t, err, "is invalid")
-				require.ErrorContains(t, err, "freight is immutable")
+				require.ErrorContains(t, err, "Freight is immutable")
 			},
 		},
 		{
@@ -1034,3 +1046,108 @@ func TestValidateFreightArtifacts(t *testing.T) {
 		})
 	}
 }
+
+func TestCompareFreight(t *testing.T) {
+	tests := []struct {
+		name       string
+		old        *kargoapi.Freight
+		new        *kargoapi.Freight
+		assertions func(*testing.T, *kargoapi.Freight, *field.Path, any, bool)
+	}{
+		{
+			name: "Equal Freights",
+			old: &kargoapi.Freight{
+				Warehouse: "warehouse1",
+				Commits:   []kargoapi.GitCommit{{ID: "commit1"}}, Images: []kargoapi.Image{{RepoURL: "image1"}},
+				Charts: []kargoapi.Chart{{Name: "chart1"}},
+			},
+			new: &kargoapi.Freight{
+				Warehouse: "warehouse1",
+				Commits:   []kargoapi.GitCommit{{ID: "commit1"}},
+				Images:    []kargoapi.Image{{RepoURL: "image1"}}, Charts: []kargoapi.Chart{{Name: "chart1"}},
+			},
+			assertions: func(t *testing.T, _ *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Nil(t, path)
+				require.Nil(t, val)
+				require.True(t, eq)
+			},
+		},
+		{
+			name: "different Warehouse",
+			old:  &kargoapi.Freight{Warehouse: "warehouse1"},
+			new:  &kargoapi.Freight{Warehouse: "warehouse2"},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("warehouse"), path)
+				require.Equal(t, freight.Warehouse, val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different number of commits",
+			old:  &kargoapi.Freight{Commits: []kargoapi.GitCommit{{ID: "commit1"}}},
+			new:  &kargoapi.Freight{Commits: []kargoapi.GitCommit{{ID: "commit1"}, {ID: "commit2"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("commits"), path)
+				require.Equal(t, freight.Commits, val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different commit contents",
+			old:  &kargoapi.Freight{Commits: []kargoapi.GitCommit{{ID: "commit1"}, {ID: "commit2"}}},
+			new:  &kargoapi.Freight{Commits: []kargoapi.GitCommit{{ID: "commit1"}, {ID: "commit3"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("commits").Index(1), path)
+				require.Equal(t, freight.Commits[1], val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different number of images",
+			old:  &kargoapi.Freight{Images: []kargoapi.Image{{RepoURL: "image1"}}},
+			new:  &kargoapi.Freight{Images: []kargoapi.Image{{RepoURL: "image1"}, {RepoURL: "image2"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("images"), path)
+				require.Equal(t, freight.Images, val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different image contents",
+			old:  &kargoapi.Freight{Images: []kargoapi.Image{{RepoURL: "image1"}}},
+			new:  &kargoapi.Freight{Images: []kargoapi.Image{{RepoURL: "image2"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("images").Index(0), path)
+				require.Equal(t, freight.Images[0], val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different number of charts",
+			old:  &kargoapi.Freight{Charts: []kargoapi.Chart{{Name: "chart1"}}},
+			new:  &kargoapi.Freight{Charts: []kargoapi.Chart{{Name: "chart1"}, {Name: "chart2"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("charts"), path)
+				require.Equal(t, freight.Charts, val)
+				require.False(t, eq)
+			},
+		},
+		{
+			name: "different chart contents",
+			old:  &kargoapi.Freight{Charts: []kargoapi.Chart{{Name: "chart1"}, {Name: "chart2"}, {Name: "chart3"}}},
+			new:  &kargoapi.Freight{Charts: []kargoapi.Chart{{Name: "chart1"}, {Name: "chart2"}, {Name: "chart4"}}},
+			assertions: func(t *testing.T, freight *kargoapi.Freight, path *field.Path, val any, eq bool) {
+				require.Equal(t, field.NewPath("charts").Index(2), path)
+				require.Equal(t, freight.Charts[2], val)
+				require.False(t, eq)
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			path, val, eq := compareFreight(tt.old, tt.new)
+			tt.assertions(t, tt.new, path, val, eq)
+		})
+	}
+}