Repository: akuity/kargo

Issue:
make git-commit step able to sign commits with configurable gpg key
A GPG key used for signing commits can already be configured at the system-level, but some users have been requesting the ability to manage such keys as the Project-level.

We already have Project-level generic Secret management.

We can already reference Project-level Secrets from expressions.

If we add a GPG key field to the git-commit step, there's not too much else (minor enhancements to our `git` package) required to complete this.


Discussion:
@fykaa: If I understand correctly, these are the things we are expected to take care of while working on this:

1. Extend `git-commit` step by adding a new optional field to allow users to specify which secret to use:
      ```
       signWithGPGKey:
          keyRef: gpg-key-secret
          passphraseRef: gpg-passphrase-secret 
      ```
2. update the `git-commit` logic to:
     - import the GPG key from the secret
     - setup git to use this key (via env vars or a temporary gpg keyring?)
     - run git commit -S using that key
   also, if the passphrase is needed, I need to make a way to provide it? Is this change required in the `git` package?

3. use project-level Secret resolution:
   - since we already have expression evaluation in place-- I think the key/passphrase references can be simply resolved using those mechanisms, like `api.secret.gpgkeyname`

4. document the new field:
   - update examples and docs so users know how to configure GPG signing per project@krancour: Looking at this closer, we probably don't want to / need to touch the commit step at all.

User information -- name, email, signing key, etc. are currently provided _at cloning time_. i.e. Look at the git-clone step.

Currently, a function called `gitUserFromEnv()` is called to get system-level git user-details.

I would add optional user, email, and signingKey fields to that step and for any of those details that have been specified, you can override what comes from `gitUserFromEnv()`.

Additional notes on the signingKey field: It should contain the "actual key" (but not really). Users already have great access to Project-level secrets from expressions. So they should use an expression here and what you will receive will be the expression already evaluated -- i.e. the _actual key_.

You will need to write that key to some temporary file, because when you hand the clone options off to git.CloneBare(), it's going to want a _path_ and not the key itself.

After that, I believe everything else should just work.@hiddeco: From my point of view, it is quite confusing for things that are used at commit time to be set at clone time. This makes sense for global configuration, but in my opinion, not very much for granular configuration where hypothetically the desired author may change for each commit.@krancour: Perhaps this doesn't need to be an either/or.

`git commit` commonly _does_ use author information from `<repo>/.gitconfig` or `~/.gitconfig`.

Today (before this change), we already allow system-level author information (specified in chart config) to be injected at clone-time by building a `.gitconfig`. It seems sensible to be able to override those details at clone-time since that is where they're already being set.

But to your point, even if less DRY, it feels more straightforward to provide these details at commit-time. `git commit` of course _does_ let you do this using `--author` and `--gpg-signing` flags.

If `git` itself offers the ability to configure author information in advance at the repo-level or system-level OR to specify that info at commit-time using flags, it seems reasonable that our `git-clone` and `git-commit` would both offer the opportunity to specify author info.

I will update the `git` package accordingly so that commits can take optional author info just like clone does.

@fykaa please update #3922 to allow the new fields on _both_ `git-clone` and `git-config`.@krancour: > I will update the git package accordingly so that commits can take optional author info just like clone does.

Actually, this is already there. We're just not using it yet.@krancour: > Actually, this is already there. We're just not using it yet.

I stand corrected. It already works for name and email, but not signing key. Will fix.@fykaa: I can work on adding support for the signing key in the git commit step so it can be passed and used like name and email. I will add that commit to the same PR as referenced by this issue.

Solution patch:
diff --git a/api/v1alpha1/generated.pb.go b/api/v1alpha1/generated.pb.go
index 088d5cf5..a1850a2f 100644
--- a/api/v1alpha1/generated.pb.go
+++ b/api/v1alpha1/generated.pb.go
@@ -2134,282 +2134,282 @@ func init() {
 }
 
 var fileDescriptor_e26b7f7bbc391025 = []byte{
-	// 4386 bytes of a gzipped FileDescriptorProto
+	// 4387 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5c, 0x4d, 0x6c, 0x23, 0x47,
-	0x76, 0x9e, 0x26, 0x45, 0x4a, 0x7c, 0xd4, 0x6f, 0x8d, 0xc6, 0xd6, 0xca, 0xb1, 0x34, 0x69, 0x1b,
-	0x86, 0x1d, 0xdb, 0x54, 0x46, 0xe3, 0xb1, 0xc7, 0x33, 0xce, 0x04, 0x22, 0x35, 0x3f, 0x9a, 0xd5,
-	0x7a, 0x94, 0xa2, 0x3c, 0x5e, 0x8f, 0x3d, 0x98, 0x94, 0xc8, 0x12, 0xd9, 0x2b, 0xb2, 0x9b, 0xee,
-	0x6a, 0xca, 0xa3, 0x24, 0x48, 0x9c, 0x5f, 0x04, 0x49, 0x10, 0x18, 0xc8, 0x06, 0xde, 0x43, 0x82,
-	0x04, 0xc9, 0x69, 0xb1, 0x40, 0xae, 0x39, 0xe4, 0xe0, 0x43, 0x2e, 0x4e, 0xb2, 0x09, 0x8c, 0xcd,
-	0x21, 0x0e, 0xb0, 0x50, 0xd6, 0x5a, 0x20, 0xe7, 0x5c, 0x72, 0x19, 0x20, 0x40, 0x50, 0x3f, 0xdd,
-	0x5d, 0xdd, 0x6c, 0x8e, 0xd8, 0xd4, 0xcf, 0x0e, 0x7c, 0x93, 0xea, 0x55, 0x7d, 0xaf, 0xea, 0x55,
-	0xbd, 0x57, 0xef, 0xbd, 0x7a, 0x4d, 0x78, 0xad, 0x61, 0x79, 0xcd, 0xee, 0x56, 0xa9, 0xe6, 0xb4,
-	0x97, 0xc8, 0x4e, 0xd7, 0xf2, 0xf6, 0x96, 0x76, 0x88, 0xdb, 0x70, 0x96, 0x48, 0xc7, 0x5a, 0xda,
-	0xbd, 0x40, 0x5a, 0x9d, 0x26, 0xb9, 0xb0, 0xd4, 0xa0, 0x36, 0x75, 0x89, 0x47, 0xeb, 0xa5, 0x8e,
-	0xeb, 0x78, 0x0e, 0x7a, 0x3e, 0x1c, 0x55, 0x92, 0xa3, 0x4a, 0x62, 0x54, 0x89, 0x74, 0xac, 0x92,
-	0x3f, 0x6a, 0xfe, 0x55, 0x0d, 0xbb, 0xe1, 0x34, 0x9c, 0x25, 0x31, 0x78, 0xab, 0xbb, 0x2d, 0xfe,
-	0x13, 0xff, 0x88, 0xbf, 0x24, 0xe8, 0xfc, 0xad, 0x9d, 0xcb, 0xac, 0x64, 0x09, 0xce, 0xf4, 0xa1,
-	0x47, 0x6d, 0x66, 0x39, 0x36, 0x7b, 0x95, 0x74, 0x2c, 0x46, 0xdd, 0x5d, 0xea, 0x2e, 0x75, 0x76,
-	0x1a, 0x9c, 0xc6, 0xa2, 0x1d, 0x96, 0x76, 0x7b, 0xa6, 0x37, 0xff, 0x5a, 0x88, 0xd4, 0x26, 0xb5,
-	0xa6, 0x65, 0x53, 0x77, 0x2f, 0x1c, 0xde, 0xa6, 0x1e, 0x49, 0x1a, 0xb5, 0xd4, 0x6f, 0x94, 0xdb,
-	0xb5, 0x3d, 0xab, 0x4d, 0x7b, 0x06, 0xbc, 0x7e, 0xd8, 0x00, 0x56, 0x6b, 0xd2, 0x36, 0x89, 0x8f,
-	0x33, 0x3f, 0x80, 0xb3, 0x2b, 0x36, 0x69, 0xed, 0x31, 0x8b, 0xe1, 0xae, 0xbd, 0xe2, 0x36, 0xba,
-	0x6d, 0x6a, 0x7b, 0xe8, 0x3c, 0x8c, 0xd8, 0xa4, 0x4d, 0xe7, 0x8c, 0xf3, 0xc6, 0x8b, 0x85, 0xf2,
-	0xf8, 0xe7, 0xfb, 0x8b, 0x67, 0x0e, 0xf6, 0x17, 0x47, 0xde, 0x26, 0x6d, 0x8a, 0x05, 0x05, 0x3d,
-	0x07, 0xb9, 0x5d, 0xd2, 0xea, 0xd2, 0xb9, 0x8c, 0xe8, 0x32, 0xa1, 0xba, 0xe4, 0xee, 0xf2, 0x46,
-	0x2c, 0x69, 0xe6, 0xef, 0x66, 0x23, 0xf0, 0xdf, 0xa2, 0x1e, 0xa9, 0x13, 0x8f, 0xa0, 0x36, 0xe4,
-	0x5b, 0x64, 0x8b, 0xb6, 0xd8, 0x9c, 0x71, 0x3e, 0xfb, 0x62, 0x71, 0xf9, 0x7a, 0x69, 0x90, 0x4d,
-	0x2c, 0x25, 0x40, 0x95, 0xd6, 0x05, 0xce, 0x75, 0xdb, 0x73, 0xf7, 0xca, 0x93, 0x6a, 0x12, 0x79,
-	0xd9, 0x88, 0x15, 0x13, 0xf4, 0xdb, 0x06, 0x14, 0x89, 0x6d, 0x3b, 0x1e, 0xf1, 0xf8, 0x36, 0xcd,
-	0x65, 0x04, 0xd3, 0xdb, 0xc3, 0x33, 0x5d, 0x09, 0xc1, 0x24, 0xe7, 0xb3, 0x8a, 0x73, 0x51, 0xa3,
-	0x60, 0x9d, 0xe7, 0xfc, 0x9b, 0x50, 0xd4, 0xa6, 0x8a, 0xa6, 0x21, 0xbb, 0x43, 0xf7, 0xa4, 0x7c,
-	0x31, 0xff, 0x13, 0xcd, 0x46, 0x04, 0xaa, 0x24, 0x78, 0x25, 0x73, 0xd9, 0x98, 0xbf, 0x06, 0xd3,
-	0x71, 0x86, 0x69, 0xc6, 0x9b, 0x7f, 0x6a, 0xc0, 0xac, 0xb6, 0x0a, 0x4c, 0xb7, 0xa9, 0x4b, 0xed,
-	0x1a, 0x45, 0x4b, 0x50, 0xe0, 0x7b, 0xc9, 0x3a, 0xa4, 0xe6, 0x6f, 0xf5, 0x8c, 0x5a, 0x48, 0xe1,
-	0x6d, 0x9f, 0x80, 0xc3, 0x3e, 0xc1, 0xb1, 0xc8, 0x3c, 0xee, 0x58, 0x74, 0x9a, 0x84, 0xd1, 0xb9,
-	0x6c, 0xf4, 0x58, 0x6c, 0xf0, 0x46, 0x2c, 0x69, 0xe6, 0x03, 0xf8, 0x86, 0x3f, 0x9f, 0x4d, 0xda,
-	0xee, 0xb4, 0x88, 0x47, 0xc3, 0x49, 0x1d, 0x7e, 0xf4, 0xce, 0xc3, 0xc8, 0x8e, 0x65, 0xd7, 0xe3,
-	0xb3, 0xf8, 0xa6, 0x65, 0xd7, 0xb1, 0xa0, 0x98, 0x3b, 0x30, 0xb1, 0xd2, 0xe9, 0xb8, 0xce, 0x2e,
-	0xad, 0x57, 0x3d, 0xd2, 0xa0, 0xe8, 0x1e, 0x00, 0x51, 0x0d, 0x2b, 0x9e, 0x80, 0x2e, 0x2e, 0xff,
-	0x42, 0x49, 0xea, 0x4c, 0x49, 0xd7, 0x99, 0x52, 0x67, 0xa7, 0xc1, 0x1b, 0x58, 0x89, 0xab, 0x66,
-	0x69, 0xf7, 0x42, 0x69, 0xd3, 0x6a, 0xd3, 0xf2, 0xe4, 0xc1, 0xfe, 0x22, 0xac, 0x04, 0x08, 0x58,
-	0x43, 0x33, 0x7f, 0xc7, 0x80, 0x73, 0x2b, 0x6e, 0xc3, 0xa9, 0xac, 0xae, 0x74, 0x3a, 0xb7, 0x28,
-	0x69, 0x79, 0xcd, 0xaa, 0x47, 0xbc, 0x2e, 0x43, 0xd7, 0x20, 0xcf, 0xc4, 0x5f, 0x6a, 0x31, 0x2f,
-	0xf8, 0xe7, 0x53, 0xd2, 0x1f, 0xed, 0x2f, 0xce, 0x26, 0x0c, 0xa4, 0x58, 0x8d, 0x42, 0x2f, 0xc1,
-	0x68, 0x9b, 0x32, 0x46, 0x1a, 0xbe, 0xc4, 0xa7, 0x14, 0xc0, 0xe8, 0xb7, 0x64, 0x33, 0xf6, 0xe9,
-	0xe6, 0x3f, 0x67, 0x60, 0x2a, 0xc0, 0x52, 0xec, 0x4f, 0x60, 0x7b, 0xbb, 0x30, 0xde, 0xd4, 0x56,
-	0x28, 0x76, 0xb9, 0xb8, 0x7c, 0x75, 0x40, 0x4d, 0x4a, 0x12, 0x52, 0x79, 0x56, 0xb1, 0x19, 0xd7,
-	0x5b, 0x71, 0x84, 0x0d, 0x6a, 0x03, 0xb0, 0x3d, 0xbb, 0xa6, 0x98, 0x8e, 0x08, 0xa6, 0x6f, 0xa6,
-	0x64, 0x5a, 0x0d, 0x00, 0xca, 0x48, 0xb1, 0x84, 0xb0, 0x0d, 0x6b, 0x0c, 0xcc, 0xbf, 0x33, 0xe0,
-	0x6c, 0xc2, 0x38, 0xf4, 0x56, 0x6c, 0x3f, 0x9f, 0xef, 0xd9, 0x4f, 0xd4, 0x33, 0x2c, 0xdc, 0xcd,
-	0x57, 0x60, 0xcc, 0xa5, 0xbb, 0x16, 0xbf, 0x29, 0x94, 0x84, 0xa7, 0xd5, 0xf8, 0x31, 0xac, 0xda,
-	0x71, 0xd0, 0x03, 0xbd, 0x0c, 0x05, 0xff, 0x6f, 0x2e, 0xe6, 0x2c, 0x57, 0x26, 0xbe, 0x71, 0x7e,
-	0x57, 0x86, 0x43, 0xba, 0xf9, 0x5b, 0x90, 0xab, 0x34, 0x89, 0xeb, 0xf1, 0x13, 0xe3, 0xd2, 0x8e,
-	0xf3, 0x0e, 0x5e, 0x57, 0x53, 0x0c, 0x4e, 0x0c, 0x96, 0xcd, 0xd8, 0xa7, 0x0f, 0xb0, 0xd9, 0x2f,
-	0xc1, 0xe8, 0x2e, 0x75, 0xc5, 0x7c, 0xb3, 0x51, 0xb0, 0xbb, 0xb2, 0x19, 0xfb, 0x74, 0xf3, 0xdf,
-	0x0d, 0x98, 0x15, 0x33, 0x58, 0xb5, 0x58, 0xcd, 0xd9, 0xa5, 0xee, 0x1e, 0xa6, 0xac, 0xdb, 0x3a,
-	0xe6, 0x09, 0xad, 0xc2, 0x34, 0xa3, 0xed, 0x5d, 0xea, 0x56, 0x1c, 0x9b, 0x79, 0x2e, 0xb1, 0x6c,
-	0x4f, 0xcd, 0x6c, 0x4e, 0xf5, 0x9e, 0xae, 0xc6, 0xe8, 0xb8, 0x67, 0x04, 0x7a, 0x11, 0xc6, 0xd4,
-	0xb4, 0xf9, 0x51, 0xe2, 0x82, 0x1d, 0xe7, 0x7b, 0xa0, 0xd6, 0xc4, 0x70, 0x40, 0x35, 0xff, 0xdb,
-	0x80, 0x19, 0xb1, 0xaa, 0x6a, 0x77, 0x8b, 0xd5, 0x5c, 0xab, 0xc3, 0x0d, 0xf0, 0x93, 0xb8, 0xa4,
-	0x6b, 0x30, 0x59, 0xf7, 0x05, 0xbf, 0x6e, 0xb5, 0x2d, 0x4f, 0xe8, 0x48, 0xae, 0xfc, 0x94, 0xc2,
-	0x98, 0x5c, 0x8d, 0x50, 0x71, 0xac, 0xb7, 0xdc, 0xbe, 0x56, 0x97, 0x79, 0xd4, 0xdd, 0x70, 0x9d,
-	0xb6, 0xc3, 0xd7, 0xb9, 0x49, 0xd8, 0x0e, 0xfa, 0x55, 0x18, 0x6b, 0xab, 0x4b, 0x4f, 0x59, 0xcd,
-	0x5f, 0x1c, 0xcc, 0x6a, 0xde, 0xd9, 0xfa, 0x0e, 0xad, 0x79, 0xfc, 0xc2, 0x0c, 0xb5, 0x2d, 0x6c,
-	0xc3, 0x01, 0x2a, 0x7a, 0x0f, 0x46, 0x58, 0x87, 0xd6, 0x84, 0x88, 0x8a, 0xcb, 0x6f, 0x0c, 0xa6,
-	0xd4, 0x91, 0x49, 0x56, 0x3b, 0xb4, 0x16, 0xca, 0x96, 0xff, 0x87, 0x05, 0xa4, 0xf9, 0x9f, 0x06,
-	0xcc, 0x25, 0xad, 0x6a, 0xdd, 0x62, 0x1e, 0xfa, 0xa0, 0x67, 0x65, 0xa5, 0xc1, 0x56, 0xc6, 0x47,
-	0x8b, 0x75, 0x05, 0xda, 0xeb, 0xb7, 0x68, 0xab, 0x7a, 0x00, 0x39, 0xcb, 0xa3, 0x6d, 0xdf, 0xd5,
-	0xb8, 0x32, 0xd8, 0xb2, 0x92, 0x26, 0x1b, 0x5e, 0xa1, 0x6b, 0x1c, 0x10, 0x4b, 0x5c, 0xf3, 0x7d,
-	0x18, 0xaf, 0x74, 0x5d, 0x97, 0xda, 0x9e, 0xbc, 0xe0, 0xbe, 0x09, 0x39, 0x66, 0xd9, 0xca, 0xce,
-	0xa7, 0xbb, 0xdb, 0x0a, 0x1c, 0xbc, 0xca, 0x07, 0x63, 0x89, 0x61, 0xfe, 0x45, 0x16, 0xce, 0xfa,
-	0x27, 0x86, 0xd6, 0x57, 0x5c, 0xcf, 0xda, 0x26, 0x35, 0x8f, 0xa1, 0x3a, 0x8c, 0xd7, 0xc3, 0x66,
-	0x4f, 0x19, 0xe2, 0x34, 0xbc, 0x02, 0x63, 0xaf, 0xc1, 0x7b, 0x38, 0x82, 0x8a, 0xde, 0x85, 0x6c,
-	0xc3, 0xf2, 0x94, 0x67, 0x78, 0x79, 0x30, 0xc9, 0xdd, 0xb4, 0xe2, 0x96, 0xa7, 0x5c, 0x54, 0xac,
-	0xb2, 0x37, 0x2d, 0x0f, 0x73, 0x44, 0xb4, 0x05, 0x79, 0xab, 0x4d, 0x1a, 0x34, 0xe5, 0xae, 0xac,
-	0xf1, 0x31, 0x71, 0xf4, 0xc0, 0xd5, 0x14, 0x54, 0x86, 0x15, 0x32, 0xe7, 0x51, 0xe3, 0x16, 0x43,
-	0xda, 0xec, 0xc1, 0x77, 0x3e, 0xc1, 0x76, 0x86, 0x3c, 0x04, 0x95, 0x61, 0x85, 0x6c, 0x7e, 0x99,
-	0x81, 0xe9, 0x50, 0x7e, 0x15, 0xa7, 0xdd, 0xb6, 0x3c, 0x34, 0x0f, 0x19, 0xab, 0xae, 0x0c, 0x12,
-	0xa8, 0x81, 0x99, 0xb5, 0x55, 0x9c, 0xb1, 0xea, 0xe8, 0x05, 0xc8, 0x6f, 0xb9, 0xc4, 0xae, 0x35,
-	0x95, 0x21, 0x0a, 0x80, 0xcb, 0xa2, 0x15, 0x2b, 0x2a, 0x7a, 0x16, 0xb2, 0x1e, 0x69, 0x28, 0xfb,
-	0x13, 0xc8, 0x6f, 0x93, 0x34, 0x30, 0x6f, 0xe7, 0x86, 0x8f, 0x75, 0x85, 0x0e, 0x8b, 0x9d, 0xd7,
-	0x0c, 0x5f, 0x55, 0x36, 0x63, 0x9f, 0xce, 0x39, 0x92, 0xae, 0xd7, 0x74, 0xdc, 0xb9, 0x5c, 0x94,
-	0xe3, 0x8a, 0x68, 0xc5, 0x8a, 0xca, 0x5d, 0x94, 0x9a, 0x98, 0xbf, 0x47, 0xdd, 0xb9, 0x7c, 0xd4,
-	0x45, 0xa9, 0xf8, 0x04, 0x1c, 0xf6, 0x41, 0xf7, 0xa1, 0x58, 0x73, 0x29, 0xf1, 0x1c, 0x77, 0x95,
-	0x78, 0x74, 0x6e, 0x34, 0xf5, 0x09, 0x9c, 0xe2, 0x5e, 0x7a, 0x25, 0x84, 0xc0, 0x3a, 0x9e, 0xf9,
-	0xf7, 0x59, 0x98, 0x0b, 0x45, 0x2b, 0xf6, 0x36, 0xf4, 0x4c, 0x95, 0x78, 0x8c, 0x3e, 0xe2, 0x79,
-	0x01, 0xf2, 0x75, 0xab, 0x41, 0x99, 0x17, 0x97, 0xf2, 0xaa, 0x68, 0xc5, 0x8a, 0x8a, 0xfe, 0x20,
-	0x16, 0x8d, 0xe4, 0xc4, 0x41, 0xb9, 0x33, 0xd8, 0x41, 0xe9, 0x37, 0xb9, 0x21, 0x42, 0x12, 0xb4,
-	0x0c, 0xd0, 0xb0, 0x3c, 0x75, 0x69, 0xa9, 0x5d, 0x0f, 0x8c, 0xf5, 0xcd, 0x80, 0x82, 0xb5, 0x5e,
-	0xe8, 0x5d, 0x28, 0x08, 0x79, 0x0d, 0xa9, 0xff, 0xc2, 0x85, 0xa9, 0xf8, 0x00, 0x38, 0xc4, 0x3a,
-	0x72, 0x90, 0xf3, 0x3e, 0xa0, 0xeb, 0x0f, 0x3b, 0x2e, 0x65, 0xfc, 0xea, 0xbe, 0x4b, 0x5c, 0x8b,
-	0x6c, 0xb5, 0xe8, 0x71, 0xc5, 0xb1, 0x5f, 0x8c, 0xc0, 0xe8, 0x0d, 0x97, 0x5a, 0x8d, 0xa6, 0x77,
-	0x0a, 0x57, 0xe2, 0x73, 0x90, 0x23, 0x2d, 0x8b, 0x30, 0x71, 0xba, 0xb5, 0x29, 0xad, 0xf0, 0x46,
-	0x2c, 0x69, 0xe8, 0x7d, 0xc8, 0x3b, 0xae, 0xd5, 0xb0, 0xec, 0xb9, 0x82, 0x98, 0xc4, 0xc5, 0xc1,
-	0xce, 0x8f, 0x5a, 0xc5, 0x1d, 0x31, 0x34, 0x3c, 0xa2, 0xf2, 0x7f, 0xac, 0x20, 0xd1, 0x3d, 0x18,
-	0x95, 0x2a, 0xe7, 0x9b, 0xb1, 0xa5, 0x81, 0xcd, 0xb0, 0xd4, 0xda, 0xd0, 0x34, 0xc8, 0xff, 0x19,
-	0xf6, 0x01, 0x51, 0x35, 0xb0, 0xc2, 0x23, 0x02, 0xfa, 0xe5, 0x14, 0x56, 0xb8, 0xaf, 0xd9, 0xad,
-	0x06, 0x66, 0x37, 0x97, 0x06, 0x54, 0x18, 0xd6, 0x7e, 0x76, 0x96, 0x8b, 0x58, 0xb9, 0xfb, 0xf9,
-	0x21, 0x44, 0xac, 0x62, 0x8d, 0xc9, 0x68, 0x8c, 0xe0, 0x47, 0x03, 0xe6, 0x77, 0xb3, 0x30, 0xa3,
-	0x7a, 0x56, 0x9c, 0x56, 0x8b, 0xd6, 0x84, 0x6f, 0x29, 0xad, 0x78, 0x36, 0xd1, 0x8a, 0x5b, 0xbe,
-	0x4f, 0x21, 0x6f, 0xc6, 0x72, 0xaa, 0xd9, 0x84, 0x3c, 0x4a, 0xc2, 0x8f, 0x90, 0x36, 0x22, 0xd8,
-	0x25, 0xd5, 0x4b, 0x79, 0x17, 0xe8, 0xf7, 0x0d, 0x38, 0xbb, 0x4b, 0x5d, 0x6b, 0xdb, 0xaa, 0x09,
-	0x7d, 0xbc, 0x65, 0x31, 0xcf, 0x71, 0xf7, 0xd4, 0xbd, 0xf9, 0xfa, 0x60, 0x9c, 0xef, 0x6a, 0x00,
-	0x6b, 0xf6, 0xb6, 0x53, 0x7e, 0x46, 0x71, 0x3b, 0x7b, 0xb7, 0x17, 0x1a, 0x27, 0xf1, 0x9b, 0xef,
-	0x00, 0x84, 0xb3, 0x4d, 0x30, 0x07, 0xeb, 0xba, 0xf2, 0x0e, 0x3c, 0x31, 0x7f, 0xb1, 0xbe, 0xed,
-	0xd4, 0xcd, 0xc8, 0x67, 0x06, 0x14, 0x15, 0xfd, 0x14, 0xdc, 0x44, 0x1c, 0x75, 0x13, 0x5f, 0x4d,
-	0x35, 0xff, 0x3e, 0x9e, 0xa1, 0x0b, 0x13, 0x11, 0x25, 0x47, 0x97, 0x54, 0xba, 0x44, 0xda, 0xc0,
-	0x9f, 0xd7, 0xd3, 0x25, 0x8f, 0xf6, 0x17, 0x67, 0x22, 0x9d, 0xc3, 0x1c, 0xca, 0xe1, 0xb1, 0xcb,
-	0x95, 0xb1, 0xef, 0xfd, 0xf5, 0xe2, 0x99, 0x8f, 0x7f, 0x7c, 0xfe, 0x8c, 0xf9, 0x69, 0x16, 0xa6,
-	0xe3, 0x52, 0x1d, 0xc0, 0xf6, 0x86, 0x36, 0x6c, 0xec, 0x44, 0x6d, 0x58, 0xe6, 0xe4, 0x6c, 0x58,
-	0xf6, 0x24, 0x6c, 0xd8, 0xc8, 0xb1, 0xd9, 0x30, 0xf3, 0xdf, 0x0c, 0x98, 0x0c, 0x76, 0xe6, 0xc3,
-	0x2e, 0xf7, 0x3f, 0x42, 0xa9, 0x1b, 0xc7, 0x2f, 0xf5, 0x07, 0x30, 0xca, 0x9c, 0xae, 0x5b, 0x13,
-	0x4e, 0x36, 0x47, 0x7f, 0x2d, 0x9d, 0xd1, 0x94, 0x63, 0x35, 0xcf, 0x52, 0x36, 0x60, 0x1f, 0xd5,
-	0xfc, 0x2c, 0x13, 0x2c, 0x48, 0xd1, 0xa4, 0xe3, 0xe5, 0x72, 0xb7, 0x94, 0x2f, 0x68, 0x4c, 0x77,
-	0xbc, 0x78, 0x2b, 0x56, 0x54, 0x64, 0x0a, 0x7b, 0xee, 0xfb, 0xff, 0x85, 0x32, 0x28, 0xb3, 0x2c,
-	0x36, 0x41, 0x52, 0x50, 0x07, 0xa6, 0x5d, 0xfa, 0x61, 0xd7, 0x72, 0x69, 0xbd, 0xea, 0x90, 0x1d,
-	0xee, 0xb4, 0xa8, 0x24, 0xd7, 0x80, 0x7a, 0xbf, 0xda, 0x75, 0x85, 0x09, 0x2b, 0xcf, 0xf2, 0xd8,
-	0x1d, 0xc7, 0xb0, 0x70, 0x0f, 0x3a, 0x72, 0x60, 0x96, 0xec, 0x12, 0xab, 0x45, 0xb6, 0xac, 0x96,
-	0xe5, 0xed, 0x55, 0x3d, 0x97, 0x78, 0xb4, 0xb1, 0xa7, 0x5c, 0xec, 0xab, 0x6a, 0x2d, 0xb3, 0x2b,
-	0x09, 0x7d, 0x1e, 0xed, 0x2f, 0x3e, 0xa3, 0x64, 0x91, 0x44, 0xc6, 0x89, 0xc0, 0xe6, 0x7f, 0xe5,
-	0x02, 0x0b, 0xa1, 0xf2, 0x5a, 0xbf, 0x0e, 0xc5, 0x9a, 0x0c, 0x26, 0x5b, 0x7b, 0x6b, 0xb6, 0x3a,
-	0xd3, 0xab, 0x43, 0xdc, 0x76, 0xa5, 0x4a, 0x08, 0x13, 0xf3, 0x42, 0x35, 0x0a, 0xd6, 0xb9, 0xa1,
-	0x8f, 0x00, 0xa4, 0xe9, 0xa7, 0xf5, 0x35, 0x5b, 0xdd, 0x6d, 0x95, 0x61, 0x78, 0xdf, 0x0d, 0x50,
-	0x24, 0xeb, 0xc0, 0xc9, 0x0a, 0x09, 0x58, 0x63, 0xc5, 0x57, 0xed, 0x67, 0x71, 0x6f, 0x38, 0xae,
-	0x32, 0x12, 0x43, 0xad, 0x7a, 0x25, 0x84, 0x89, 0xfb, 0xde, 0x21, 0x05, 0xeb, 0xdc, 0xe6, 0x5d,
-	0x98, 0x8e, 0xcb, 0x2a, 0xe1, 0x7e, 0xbb, 0x15, 0xbd, 0xdf, 0x96, 0x07, 0xb4, 0x08, 0x5a, 0x62,
-	0x40, 0x7f, 0x47, 0x70, 0x61, 0x2a, 0x26, 0xa3, 0x04, 0x96, 0x6b, 0x51, 0x96, 0x17, 0xd3, 0xdc,
-	0xf5, 0x2a, 0xdb, 0xae, 0xf3, 0x64, 0x30, 0x1d, 0x97, 0xce, 0xb1, 0x31, 0x8d, 0xa4, 0xf8, 0xf5,
-	0x4b, 0xfc, 0x2f, 0x33, 0x50, 0x08, 0xcc, 0x78, 0x9a, 0x7c, 0x9d, 0x74, 0xbf, 0x32, 0x87, 0x04,
-	0xd1, 0xd9, 0x41, 0x82, 0xe8, 0x91, 0xfe, 0x41, 0xb4, 0x9f, 0xd3, 0xcf, 0x3f, 0x3e, 0xa7, 0xaf,
-	0x05, 0xd1, 0xa3, 0x83, 0x07, 0xd1, 0x63, 0x87, 0x07, 0xd1, 0xe6, 0xdf, 0x18, 0x80, 0x7a, 0x33,
-	0x26, 0x69, 0x04, 0x45, 0xe2, 0x97, 0xeb, 0xeb, 0x69, 0xc3, 0xd7, 0xc3, 0xee, 0x58, 0xf3, 0xb3,
-	0x1c, 0x4c, 0xdd, 0xb4, 0x86, 0x4e, 0xbd, 0x7a, 0xf0, 0xb4, 0x44, 0xaa, 0x52, 0xe5, 0xf8, 0x06,
-	0x96, 0x55, 0xee, 0xef, 0x15, 0x35, 0xf4, 0xe9, 0x4a, 0x72, 0xb7, 0x47, 0xfd, 0x49, 0xb8, 0x1f,
-	0xf4, 0xc0, 0x87, 0xe4, 0x2a, 0x4c, 0x30, 0xcf, 0xb5, 0x6a, 0x9e, 0x4c, 0xee, 0xb2, 0xb9, 0xa2,
-	0xb8, 0xb9, 0xce, 0xa9, 0xee, 0x13, 0x55, 0x9d, 0x88, 0xa3, 0x7d, 0x13, 0x73, 0xc6, 0x23, 0xa9,
-	0x73, 0xc6, 0x4b, 0x50, 0x20, 0xad, 0x96, 0xf3, 0xd1, 0x26, 0x69, 0x30, 0x95, 0xa5, 0x09, 0x4e,
-	0xcd, 0x8a, 0x4f, 0xc0, 0x61, 0x1f, 0x54, 0x02, 0xb0, 0x1a, 0xb6, 0xe3, 0x52, 0x31, 0x22, 0x2f,
-	0xae, 0x50, 0xf1, 0x2e, 0xb6, 0x16, 0xb4, 0x62, 0xad, 0x07, 0xaa, 0xc2, 0x39, 0xcb, 0x66, 0xb4,
-	0xd6, 0x75, 0x69, 0x75, 0xc7, 0xea, 0x6c, 0xae, 0x57, 0x85, 0x95, 0xd8, 0x13, 0xa7, 0x79, 0xac,
-	0xfc, 0xac, 0x62, 0x76, 0x6e, 0x2d, 0xa9, 0x13, 0x4e, 0x1e, 0x8b, 0x5e, 0x83, 0x71, 0xcb, 0xae,
-	0xb5, 0xba, 0x75, 0xba, 0x41, 0xbc, 0x26, 0x9b, 0x1b, 0x13, 0xd3, 0x98, 0x3e, 0xd8, 0x5f, 0x1c,
-	0x5f, 0xd3, 0xda, 0x71, 0xa4, 0x17, 0x1f, 0x45, 0x1f, 0x6a, 0xa3, 0x0a, 0xe1, 0xa8, 0xeb, 0x0f,
-	0xf5, 0x51, 0x7a, 0xaf, 0x84, 0xac, 0x3a, 0xa4, 0xca, 0xaa, 0xff, 0x20, 0x03, 0x79, 0xf9, 0xa8,
-	0x85, 0x2e, 0xc5, 0x5e, 0x8e, 0x9e, 0xed, 0x79, 0x39, 0x2a, 0x26, 0x3d, 0x00, 0x9a, 0x90, 0xb7,
-	0x18, 0xeb, 0x46, 0x3d, 0x96, 0x35, 0xd1, 0x82, 0x15, 0x45, 0x64, 0x1c, 0x1d, 0x7b, 0xdb, 0x6a,
-	0xa8, 0x74, 0xcc, 0x35, 0xcd, 0x4f, 0x09, 0x4b, 0x13, 0x1e, 0x04, 0xb5, 0x0b, 0xa1, 0xcb, 0x12,
-	0xe9, 0xc0, 0x7d, 0x97, 0xdb, 0xd5, 0x3b, 0x6f, 0x4b, 0x1e, 0x15, 0x81, 0x88, 0x15, 0x32, 0xe7,
-	0xe1, 0x74, 0xbd, 0x4e, 0xd7, 0x13, 0x07, 0xe5, 0x98, 0x78, 0xdc, 0x11, 0x88, 0x58, 0x21, 0x9b,
-	0x9f, 0x1a, 0x30, 0x25, 0x65, 0x50, 0x69, 0xd2, 0xda, 0x4e, 0xd5, 0xa3, 0x1d, 0x1e, 0x42, 0x74,
-	0x19, 0x65, 0xf1, 0x10, 0xe2, 0x1d, 0x46, 0x19, 0x16, 0x14, 0x6d, 0xf5, 0x99, 0x93, 0x5a, 0xbd,
-	0x79, 0x19, 0xb4, 0xcd, 0x11, 0xaf, 0xb2, 0xf2, 0x71, 0x52, 0x5e, 0x61, 0xd9, 0xd0, 0x08, 0xc9,
-	0x5e, 0x7b, 0xd8, 0xa7, 0x9b, 0x07, 0x19, 0xc8, 0x09, 0x2f, 0x3f, 0x8d, 0xe5, 0x8a, 0xa6, 0xe5,
-	0x32, 0x03, 0xa5, 0xe5, 0x0e, 0xc9, 0xdc, 0x86, 0xa9, 0xc9, 0x91, 0xc7, 0xa6, 0x26, 0x59, 0x52,
-	0x66, 0xf2, 0xad, 0x14, 0xc1, 0xcd, 0x30, 0x95, 0x11, 0x47, 0xcd, 0xfc, 0xfd, 0xd4, 0x80, 0xd9,
-	0xa4, 0x1c, 0x7d, 0x1a, 0x99, 0xbf, 0x02, 0x63, 0x9d, 0x16, 0xf1, 0xb6, 0x1d, 0xb7, 0x1d, 0x7f,
-	0x9b, 0xdd, 0x50, 0xed, 0x38, 0xe8, 0x81, 0x5c, 0x00, 0xd7, 0x0f, 0x73, 0xfd, 0x10, 0xf0, 0xda,
-	0xd1, 0xf2, 0xb7, 0xe1, 0x0e, 0x07, 0x4d, 0x0c, 0x6b, 0x5c, 0xcc, 0x3f, 0xce, 0xc1, 0x8c, 0x18,
-	0x32, 0xec, 0x85, 0x38, 0xcc, 0xb1, 0xea, 0xc0, 0x53, 0x22, 0x38, 0xed, 0xbd, 0x43, 0xe5, 0x49,
-	0xbb, 0xac, 0xc6, 0x3f, 0xb5, 0x96, 0xd8, 0xeb, 0x51, 0x5f, 0x0a, 0xee, 0x83, 0xdb, 0x7b, 0x31,
-	0xc2, 0xd7, 0xef, 0x62, 0xd4, 0x0f, 0xdb, 0xe8, 0xa1, 0x87, 0xad, 0xef, 0x35, 0x3a, 0x76, 0x84,
-	0x6b, 0xb4, 0xf7, 0x6a, 0x2b, 0xa4, 0xba, 0xda, 0xfe, 0x2a, 0x03, 0xa3, 0x1b, 0xae, 0x23, 0xde,
-	0x7a, 0x4e, 0x3e, 0x21, 0xfe, 0xce, 0x90, 0x6f, 0xc4, 0x1c, 0x4a, 0xda, 0x72, 0xf1, 0x46, 0x3c,
-	0x16, 0x7d, 0x1f, 0xd6, 0xf2, 0xbb, 0xd9, 0x34, 0x61, 0x87, 0x02, 0x3e, 0x24, 0xbf, 0xfb, 0x43,
-	0x03, 0x26, 0x22, 0x53, 0x78, 0x82, 0xdf, 0xd2, 0x63, 0x72, 0x4a, 0x7a, 0x4b, 0xff, 0x17, 0x03,
-	0x66, 0x22, 0x3d, 0x4f, 0x21, 0x3b, 0xfa, 0xed, 0x68, 0x76, 0xf4, 0xe2, 0x10, 0xeb, 0xe9, 0x93,
-	0x23, 0xfd, 0xb3, 0xf8, 0x6a, 0xf8, 0x4a, 0xd1, 0x6f, 0xc2, 0x4c, 0xc7, 0x7f, 0x7a, 0xdf, 0x70,
-	0x5a, 0x56, 0xcd, 0xa2, 0x7e, 0xb2, 0xfd, 0x52, 0xca, 0xba, 0x04, 0x31, 0x7c, 0xaf, 0xfc, 0x0d,
-	0xc5, 0x7d, 0x66, 0x23, 0x8e, 0x8b, 0x7b, 0x59, 0x89, 0xdc, 0xb3, 0x9a, 0xd5, 0x13, 0x9b, 0x7b,
-	0x56, 0xf3, 0xeb, 0x23, 0xd7, 0x1f, 0x19, 0x30, 0xae, 0xa9, 0x07, 0x43, 0x4d, 0x80, 0x8f, 0x88,
-	0x4b, 0x9b, 0x4e, 0xe0, 0xc6, 0x0d, 0x9c, 0x11, 0x7c, 0xd7, 0x1f, 0x27, 0x90, 0xc2, 0x93, 0x1f,
-	0xb4, 0x33, 0xac, 0x61, 0xa3, 0x6f, 0x6b, 0xc9, 0x3d, 0xa9, 0x5b, 0x03, 0x71, 0x11, 0xb9, 0x03,
-	0xc9, 0x41, 0xd7, 0x64, 0x2d, 0x25, 0x68, 0x7e, 0x3f, 0x13, 0x68, 0xb2, 0xca, 0x97, 0xd5, 0x00,
-	0x6a, 0x8e, 0x5d, 0xb7, 0xbc, 0xa0, 0x38, 0xab, 0xb8, 0xbc, 0x34, 0xd8, 0xd6, 0x54, 0xfc, 0x71,
-	0xe1, 0x82, 0x82, 0x26, 0x86, 0x35, 0x58, 0x74, 0xd1, 0xaf, 0xa4, 0x8c, 0x46, 0x0c, 0xb2, 0x92,
-	0xf2, 0xd1, 0xfe, 0xa2, 0x2f, 0x68, 0xbd, 0xb2, 0x32, 0x45, 0xc5, 0x20, 0xaa, 0x42, 0x8e, 0x9b,
-	0x2a, 0xbf, 0x9c, 0x6e, 0x39, 0xb5, 0xf1, 0x63, 0xaa, 0x72, 0x84, 0xff, 0x89, 0x25, 0x96, 0xf9,
-	0xb7, 0x19, 0x28, 0x04, 0x67, 0xfd, 0xd4, 0x6f, 0x86, 0x8b, 0x29, 0xb5, 0xb4, 0x9f, 0xb5, 0x43,
-	0xf7, 0x63, 0x37, 0x43, 0x5a, 0xf5, 0x3f, 0xe4, 0x6e, 0xf8, 0x47, 0x79, 0x37, 0xc8, 0xbe, 0xa7,
-	0xa0, 0xea, 0x9b, 0x51, 0x55, 0x5f, 0x4a, 0xb9, 0x9a, 0x3e, 0xca, 0xfe, 0x71, 0x06, 0xa6, 0x62,
-	0x06, 0x0f, 0x3d, 0x27, 0x0e, 0x55, 0xc3, 0x3f, 0xb4, 0xc1, 0x40, 0x95, 0xa2, 0x13, 0x34, 0xb4,
-	0xcb, 0x7d, 0xbc, 0xc0, 0xfb, 0x73, 0x5c, 0x25, 0xe4, 0x5f, 0x1a, 0xca, 0xc6, 0xfa, 0x20, 0xe5,
-	0x19, 0xe9, 0x1e, 0x6a, 0xb8, 0x38, 0xca, 0x06, 0x6d, 0xc0, 0x2c, 0xe9, 0x7a, 0x4e, 0x00, 0x70,
-	0xdd, 0x26, 0x5b, 0x2d, 0x2a, 0xf3, 0x7d, 0x63, 0xe5, 0x9f, 0x0b, 0x32, 0xed, 0x09, 0x7d, 0x70,
-	0xe2, 0x48, 0xf3, 0xfb, 0x06, 0x3c, 0xdd, 0x67, 0x3e, 0x03, 0x3c, 0x7f, 0xb5, 0x60, 0x42, 0x14,
-	0xa8, 0x07, 0x72, 0xf0, 0x4f, 0xf1, 0x60, 0x3b, 0xaf, 0x0f, 0x95, 0xab, 0x8f, 0x34, 0xe1, 0x28,
-	0xb8, 0xf9, 0xc3, 0x0c, 0xa0, 0x60, 0xae, 0x69, 0x5e, 0xe9, 0xee, 0xc3, 0xe8, 0xb6, 0x4c, 0x77,
-	0x1f, 0xed, 0x99, 0xb5, 0x5c, 0xd4, 0x5f, 0x9a, 0x7d, 0x4c, 0xf4, 0xde, 0xf1, 0xe8, 0x1a, 0xf4,
-	0xea, 0x19, 0xba, 0x07, 0xb0, 0x6d, 0xd9, 0x16, 0x6b, 0x0e, 0x59, 0xad, 0x22, 0x9c, 0xf8, 0x1b,
-	0x01, 0x02, 0xd6, 0xd0, 0xcc, 0x3f, 0xcf, 0x68, 0x3a, 0x2c, 0xdc, 0x87, 0x81, 0xce, 0xfe, 0x4b,
-	0x51, 0x61, 0x16, 0x7a, 0x9f, 0xe0, 0x03, 0xc1, 0xdc, 0x83, 0x91, 0x5d, 0xe2, 0xfa, 0xaf, 0x81,
-	0x03, 0x16, 0xc2, 0xf5, 0xd6, 0xc0, 0x84, 0x7b, 0x7a, 0x97, 0xb8, 0x0c, 0x0b, 0x4c, 0xee, 0x5a,
-	0x31, 0x8f, 0x76, 0xfc, 0xcb, 0x2b, 0xb5, 0xe1, 0xf4, 0x68, 0x47, 0x5f, 0x20, 0xed, 0x88, 0x1b,
-	0x80, 0x76, 0x98, 0xf9, 0xdd, 0x51, 0xcd, 0x2a, 0xa8, 0xfb, 0xf2, 0x36, 0xa0, 0x16, 0x61, 0xde,
-	0x2d, 0x62, 0xd7, 0xb9, 0x2e, 0xd1, 0x6d, 0x97, 0xb2, 0xa6, 0x8a, 0xcc, 0xe6, 0x15, 0x0a, 0x5a,
-	0xef, 0xe9, 0x81, 0x13, 0x46, 0xa1, 0x4b, 0xd1, 0x6b, 0x71, 0x31, 0x7e, 0x2d, 0x4e, 0x86, 0xfa,
-	0x38, 0xe4, 0xc5, 0xa8, 0x9d, 0xf7, 0xdc, 0x09, 0x9c, 0xf7, 0xdf, 0x80, 0x99, 0xed, 0x78, 0x4d,
-	0x86, 0xaa, 0x63, 0x7b, 0x63, 0xc8, 0x92, 0x8e, 0xf2, 0xb9, 0x83, 0xf0, 0x21, 0x3f, 0x6c, 0xc6,
-	0xbd, 0x8c, 0x90, 0xe3, 0x17, 0xf0, 0x8b, 0x24, 0x9b, 0xcc, 0x9f, 0x0e, 0xac, 0x73, 0xb1, 0xf4,
-	0x5c, 0xbc, 0x74, 0x5f, 0x42, 0xe2, 0x08, 0x83, 0x98, 0x0e, 0xe6, 0x8f, 0x53, 0x07, 0xd1, 0xa5,
-	0xe0, 0xdd, 0x92, 0x4f, 0x47, 0x84, 0xb0, 0xd9, 0x9e, 0x17, 0x47, 0x4e, 0xc2, 0x7a, 0x3f, 0xf4,
-	0x89, 0x01, 0xe7, 0xf8, 0x61, 0xbd, 0xfe, 0x90, 0xd6, 0xba, 0x5c, 0x2a, 0xfe, 0x77, 0x3d, 0x73,
-	0x45, 0x21, 0x8d, 0xab, 0x83, 0xba, 0x8e, 0x09, 0x10, 0x61, 0x3c, 0x9e, 0x48, 0xc6, 0xc9, 0x8c,
-	0xd1, 0x03, 0xe9, 0x8c, 0x51, 0x91, 0xee, 0x38, 0x7a, 0x16, 0x33, 0x70, 0xcc, 0xa4, 0xdd, 0xf1,
-	0xa8, 0xf9, 0x93, 0xac, 0x6e, 0xae, 0x06, 0xcb, 0xad, 0xde, 0x83, 0x11, 0x8f, 0xb0, 0x1d, 0xa5,
-	0x05, 0x6f, 0x0d, 0x51, 0x9a, 0x1d, 0xea, 0x82, 0x88, 0xbd, 0x45, 0x93, 0xc0, 0x44, 0xf3, 0x90,
-	0x21, 0x2c, 0xfe, 0xd2, 0xb6, 0xc2, 0x70, 0x86, 0x30, 0xf1, 0x0a, 0xb7, 0xad, 0x32, 0x23, 0xe1,
-	0x2b, 0xdc, 0x36, 0xce, 0x58, 0xdb, 0xe8, 0x3d, 0xc8, 0xb9, 0xd4, 0x73, 0xf7, 0x94, 0x35, 0xbf,
-	0x3c, 0x84, 0xe1, 0xc2, 0x7c, 0xbc, 0x14, 0x91, 0xf8, 0x13, 0x4b, 0xc4, 0xc0, 0xde, 0xe6, 0x4f,
-	0xc0, 0xde, 0x86, 0x69, 0xea, 0xec, 0x89, 0xa5, 0xa9, 0x7f, 0x60, 0x68, 0x17, 0x7c, 0xb0, 0x50,
-	0xf4, 0x0e, 0x8c, 0x7a, 0x56, 0x9b, 0x3a, 0x5d, 0x2f, 0x9d, 0x67, 0x19, 0x14, 0x32, 0x08, 0x33,
-	0xb6, 0x29, 0x21, 0xb0, 0x8f, 0x85, 0xae, 0xc1, 0x24, 0x75, 0x5d, 0xc7, 0xdd, 0x6c, 0x72, 0xb3,
-	0xec, 0xb4, 0xa4, 0x1b, 0x35, 0x11, 0x66, 0x90, 0xae, 0x47, 0xa8, 0x38, 0xd6, 0xdb, 0xcf, 0x8f,
-	0x7c, 0x5d, 0xbe, 0x35, 0x50, 0xf9, 0x91, 0x53, 0xfd, 0xc8, 0x60, 0xe8, 0xfc, 0xc8, 0xa1, 0x5f,
-	0x17, 0x7c, 0x00, 0x4f, 0x25, 0xeb, 0xf1, 0xb1, 0x7c, 0x9d, 0xf7, 0x4f, 0x71, 0x59, 0x09, 0xf7,
-	0xc9, 0x57, 0x3f, 0xe3, 0x24, 0xdd, 0x9d, 0xcc, 0x71, 0xbb, 0x3b, 0xae, 0xbe, 0x14, 0xf5, 0x2d,
-	0x23, 0xba, 0xaf, 0xce, 0x99, 0x91, 0xe6, 0xeb, 0xb8, 0x1e, 0x98, 0xbe, 0x67, 0xed, 0x5f, 0x0d,
-	0x38, 0x97, 0xd8, 0x3b, 0x90, 0x61, 0xe6, 0x24, 0x65, 0x68, 0x1c, 0xb7, 0x0c, 0xbf, 0x97, 0x81,
-	0x69, 0x4c, 0x3b, 0x4e, 0xe4, 0x65, 0x63, 0xc3, 0xff, 0x0a, 0x24, 0x45, 0x4c, 0x10, 0x2b, 0x17,
-	0x28, 0x8f, 0x46, 0x3e, 0xff, 0xe0, 0xea, 0xd2, 0xf6, 0x1d, 0xc0, 0x81, 0xd5, 0xbf, 0xe7, 0xcd,
-	0x45, 0xde, 0x1c, 0xf2, 0xf5, 0x46, 0x02, 0x72, 0x64, 0x51, 0x6e, 0xa7, 0x8c, 0xfb, 0x1b, 0x29,
-	0x0a, 0xf7, 0x7a, 0x91, 0x45, 0x33, 0x96, 0x80, 0xe6, 0xa7, 0x19, 0x90, 0xf1, 0xc3, 0x29, 0x58,
-	0xc7, 0x5f, 0x89, 0x58, 0xc7, 0xa5, 0x34, 0xf9, 0xb3, 0x7e, 0x79, 0x94, 0x78, 0x6c, 0x77, 0x21,
-	0x65, 0x52, 0xee, 0x31, 0x39, 0x94, 0x7f, 0x30, 0xa0, 0x20, 0xfa, 0x9d, 0x82, 0xa1, 0xdd, 0x88,
-	0x1a, 0xda, 0x97, 0x53, 0xac, 0xa2, 0x8f, 0x81, 0xfd, 0x9f, 0xac, 0x9a, 0x7d, 0x10, 0x39, 0x36,
-	0x89, 0x5b, 0x57, 0x21, 0x51, 0xa8, 0x25, 0xbc, 0x11, 0x4b, 0x5a, 0xa0, 0xdb, 0xa3, 0x27, 0xa0,
-	0xdb, 0xbf, 0x26, 0xab, 0x1e, 0x29, 0xf3, 0x68, 0xfd, 0x46, 0x10, 0xfb, 0x64, 0x53, 0x97, 0x6f,
-	0xaa, 0x12, 0xd3, 0xf0, 0xb9, 0x0d, 0xc7, 0x50, 0x71, 0x0f, 0x1f, 0x1e, 0x0f, 0x75, 0xe2, 0xc6,
-	0x4c, 0xc5, 0x09, 0x6f, 0x0c, 0x69, 0x39, 0x65, 0x3c, 0xd4, 0xd3, 0x8c, 0x7b, 0x19, 0xa1, 0x26,
-	0x8c, 0xeb, 0x85, 0xe7, 0xea, 0x9c, 0x2e, 0xa7, 0xaf, 0x70, 0x97, 0xd5, 0x24, 0x7a, 0x0b, 0x8e,
-	0x20, 0x9b, 0x7f, 0x62, 0x00, 0x84, 0xd9, 0x66, 0xbe, 0xe7, 0x35, 0xa7, 0x6b, 0xcb, 0x34, 0x40,
-	0x36, 0xdc, 0xf3, 0x0a, 0x6f, 0xc4, 0x92, 0xc6, 0xf5, 0x47, 0x06, 0x53, 0xea, 0x50, 0x5f, 0x48,
-	0x13, 0xa7, 0xc5, 0xb2, 0xda, 0xb2, 0x11, 0x2b, 0x40, 0x73, 0x3f, 0x0f, 0x45, 0x4d, 0xcf, 0x62,
-	0x39, 0xed, 0x89, 0x93, 0xc9, 0x69, 0x27, 0x27, 0x02, 0x8a, 0x43, 0x25, 0x02, 0x2e, 0x44, 0x13,
-	0x01, 0xcf, 0xc4, 0x13, 0x01, 0x52, 0xd8, 0x91, 0x24, 0x00, 0x83, 0x49, 0x15, 0x11, 0xfb, 0x1f,
-	0x34, 0xc8, 0xdc, 0xca, 0xd0, 0x71, 0x37, 0xe2, 0x8e, 0xee, 0x8d, 0x08, 0x24, 0x8e, 0xb1, 0xe0,
-	0x8e, 0xb2, 0x6a, 0xa9, 0x76, 0xdb, 0x6d, 0xe2, 0xee, 0xcd, 0x8d, 0x8b, 0x09, 0x07, 0x8e, 0xf2,
-	0x8d, 0x08, 0x15, 0xc7, 0x7a, 0xa3, 0x8d, 0xe0, 0x0c, 0xc8, 0x22, 0xf9, 0x57, 0xd2, 0x9c, 0x01,
-	0x19, 0x28, 0x44, 0xb7, 0x5e, 0xcf, 0x85, 0x14, 0x0e, 0xc9, 0x85, 0xdc, 0x06, 0xe4, 0x6c, 0x89,
-	0x90, 0xa4, 0x7e, 0x53, 0xfe, 0x72, 0x08, 0x57, 0x92, 0xbc, 0x38, 0xb2, 0xc1, 0x86, 0xdd, 0xe9,
-	0xe9, 0x81, 0x13, 0x46, 0x71, 0x23, 0xa3, 0xa2, 0xf0, 0x40, 0x33, 0x55, 0xde, 0x23, 0x6d, 0x14,
-	0x17, 0x86, 0x95, 0xa2, 0xc8, 0xba, 0x12, 0x43, 0xc5, 0x3d, 0x7c, 0xd0, 0x87, 0x30, 0xc1, 0x8f,
-	0x50, 0xc8, 0x18, 0x8e, 0xc8, 0x58, 0xe5, 0x5b, 0x35, 0x48, 0x1c, 0xe5, 0x60, 0xfe, 0x51, 0x16,
-	0x92, 0x73, 0x00, 0xe1, 0xf7, 0x5d, 0xc6, 0x63, 0xbe, 0xef, 0x7a, 0x17, 0x0a, 0xcc, 0x23, 0xae,
-	0xfc, 0xd0, 0x2e, 0x33, 0xdc, 0x87, 0x76, 0x55, 0x1f, 0x00, 0x87, 0x58, 0xb1, 0x84, 0x4c, 0xf6,
-	0x58, 0x13, 0x32, 0xcb, 0x00, 0x22, 0xcc, 0x13, 0x46, 0x4c, 0xdc, 0x66, 0x13, 0xa1, 0x4d, 0xb8,
-	0x1e, 0x50, 0xb0, 0xd6, 0x0b, 0x5d, 0x0e, 0x7c, 0x04, 0x59, 0x6b, 0x71, 0xbe, 0xa7, 0x34, 0x2e,
-	0x9e, 0xd2, 0x4b, 0xf8, 0x79, 0x8c, 0x43, 0x4a, 0x69, 0xcd, 0xff, 0xcb, 0x40, 0xc4, 0x36, 0xa3,
-	0x3f, 0x34, 0x60, 0x86, 0xc4, 0x7e, 0x83, 0xc4, 0x77, 0x6d, 0x7f, 0x39, 0xdd, 0x0f, 0xc3, 0xf4,
-	0xfc, 0x84, 0x49, 0xf8, 0xec, 0x1b, 0xef, 0xc2, 0x70, 0x2f, 0x53, 0xf4, 0x7b, 0x06, 0x9c, 0x25,
-	0xbd, 0x3f, 0x32, 0xa3, 0x36, 0xfd, 0xcd, 0xa1, 0x7f, 0xa5, 0xa6, 0xfc, 0xf4, 0xc1, 0xfe, 0x62,
-	0xd2, 0xcf, 0xef, 0xe0, 0x24, 0x76, 0xe8, 0x7d, 0x18, 0x21, 0x6e, 0xc3, 0xcf, 0x08, 0xa7, 0x67,
-	0xeb, 0xff, 0x76, 0x50, 0xe8, 0x60, 0xac, 0xb8, 0x0d, 0x86, 0x05, 0xa8, 0xf9, 0xe3, 0x2c, 0x4c,
-	0xc7, 0xbf, 0x07, 0x53, 0xd5, 0xd6, 0x23, 0x89, 0xd5, 0xd6, 0x5c, 0x47, 0xc4, 0x9b, 0x48, 0xfc,
-	0x1b, 0x48, 0xf1, 0xb4, 0x21, 0x69, 0x81, 0x8e, 0x88, 0xaf, 0x34, 0x72, 0x47, 0xd0, 0x11, 0xf1,
-	0x69, 0x46, 0x88, 0x85, 0x2e, 0x47, 0xef, 0x16, 0x33, 0x7e, 0xb7, 0xcc, 0xe8, 0x6b, 0x19, 0x36,
-	0xcf, 0xdc, 0x86, 0xa2, 0xb6, 0x0f, 0x4a, 0x13, 0xaf, 0xa4, 0x96, 0x7b, 0x78, 0xec, 0xa6, 0x64,
-	0x99, 0x5d, 0x48, 0xd1, 0xf1, 0x43, 0xbd, 0x17, 0xd2, 0x3a, 0x52, 0x22, 0x56, 0x88, 0x4b, 0x43,
-	0x33, 0xff, 0xc3, 0x80, 0x89, 0xc8, 0x27, 0x00, 0x9c, 0x9b, 0xff, 0xa9, 0xc5, 0xf0, 0x3f, 0xb8,
-	0x73, 0x37, 0x40, 0xc0, 0x1a, 0x1a, 0xfa, 0x0e, 0x14, 0x5b, 0x8e, 0xdd, 0xa0, 0xcc, 0xab, 0x3a,
-	0x64, 0x47, 0xe9, 0x49, 0xda, 0xac, 0xd6, 0xdc, 0xc1, 0xfe, 0xe2, 0xec, 0xba, 0x84, 0xa9, 0x38,
-	0xed, 0x4e, 0x8b, 0x7a, 0xf2, 0xa3, 0x1c, 0xac, 0x83, 0x8b, 0x07, 0xed, 0xa0, 0xe2, 0xe0, 0x49,
-	0x7d, 0xd0, 0x0e, 0x4b, 0x25, 0x8e, 0xf9, 0x41, 0x3b, 0x52, 0x83, 0x71, 0xc8, 0x83, 0x76, 0xd0,
-	0xf7, 0x89, 0x7d, 0xd0, 0x0e, 0x66, 0xd8, 0x27, 0x28, 0xfb, 0xdf, 0x8c, 0xb6, 0x8a, 0x68, 0x60,
-	0x96, 0x79, 0x4c, 0x60, 0xf6, 0x01, 0x8c, 0x59, 0xb6, 0x47, 0xdd, 0x5d, 0xd2, 0x52, 0x59, 0xe9,
-	0xb4, 0x67, 0x31, 0x58, 0xea, 0x9a, 0xc2, 0xc1, 0x01, 0x22, 0x6a, 0xc1, 0x39, 0xff, 0x15, 0xc7,
-	0xa5, 0x24, 0x7c, 0x67, 0x56, 0x15, 0x98, 0xaf, 0xfb, 0xcf, 0x0d, 0x37, 0x92, 0x3a, 0x3d, 0xea,
-	0x47, 0xc0, 0xc9, 0xa0, 0x88, 0xc1, 0x04, 0xd3, 0x32, 0x12, 0xfe, 0x8d, 0x38, 0xe0, 0x0b, 0x58,
-	0x3c, 0x89, 0xa3, 0x95, 0x6d, 0xea, 0xa0, 0x38, 0xca, 0xc3, 0xfc, 0xc4, 0x80, 0xc9, 0x68, 0xb5,
-	0xcf, 0xcf, 0x3c, 0x3a, 0xfa, 0x51, 0x16, 0xa6, 0x62, 0x87, 0x3f, 0x16, 0x21, 0x15, 0x4e, 0x33,
-	0x42, 0xca, 0x0f, 0x15, 0x21, 0x25, 0x3b, 0xef, 0x23, 0x43, 0x39, 0xef, 0x57, 0xa5, 0x03, 0xad,
-	0x0e, 0xd3, 0xda, 0xaa, 0xfa, 0xce, 0x28, 0xd8, 0xe0, 0x75, 0x9d, 0x88, 0xa3, 0x7d, 0x85, 0x87,
-	0x53, 0xef, 0xfd, 0x3d, 0x19, 0xe5, 0xfd, 0xbf, 0x99, 0xb6, 0x72, 0x3a, 0x00, 0x90, 0x1e, 0x4e,
-	0x02, 0x01, 0x27, 0xb1, 0x2b, 0xdf, 0xfe, 0xfc, 0xab, 0x85, 0x33, 0x5f, 0x7c, 0xb5, 0x70, 0xe6,
-	0xcb, 0xaf, 0x16, 0xce, 0x7c, 0x7c, 0xb0, 0x60, 0x7c, 0x7e, 0xb0, 0x60, 0x7c, 0x71, 0xb0, 0x60,
-	0x7c, 0x79, 0xb0, 0x60, 0xfc, 0xe4, 0x60, 0xc1, 0xf8, 0xe4, 0xa7, 0x0b, 0x67, 0xee, 0x3d, 0x3f,
-	0xc8, 0xaf, 0x50, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x43, 0x98, 0xb9, 0xac, 0x52,
-	0x00, 0x00,
+	0x76, 0x9e, 0x26, 0x45, 0x4a, 0x7c, 0xd4, 0x6f, 0x8d, 0xc6, 0xd6, 0xca, 0xb1, 0x34, 0x69, 0x3b,
+	0x86, 0x1d, 0xdb, 0x54, 0x46, 0xe3, 0xb1, 0xc7, 0x33, 0xde, 0x09, 0x44, 0x6a, 0x7e, 0x34, 0xab,
+	0xf5, 0x28, 0x45, 0x79, 0xbc, 0x1e, 0x7b, 0x30, 0x29, 0x91, 0x25, 0xb2, 0x57, 0x64, 0x37, 0xdd,
+	0xd5, 0x94, 0x47, 0x49, 0x90, 0x38, 0xbf, 0x08, 0x90, 0x20, 0x31, 0x90, 0x0d, 0xbc, 0x87, 0x04,
+	0x09, 0x92, 0xd3, 0x62, 0x81, 0x5c, 0x73, 0xc8, 0xc1, 0x87, 0x5c, 0x9c, 0x64, 0x13, 0x18, 0x9b,
+	0x43, 0x1c, 0x60, 0xa1, 0xac, 0xb5, 0x40, 0xce, 0xb9, 0xe4, 0x32, 0x40, 0x80, 0xa0, 0x7e, 0xba,
+	0xbb, 0xba, 0xd9, 0x1c, 0xb1, 0xa9, 0x9f, 0x1d, 0xec, 0x4d, 0xaa, 0x57, 0xf5, 0xbd, 0xaa, 0x57,
+	0xf5, 0x5e, 0xbd, 0xf7, 0xea, 0x35, 0xe1, 0xb5, 0x86, 0xe5, 0x35, 0xbb, 0x5b, 0xa5, 0x9a, 0xd3,
+	0x5e, 0x22, 0x3b, 0x5d, 0xcb, 0xdb, 0x5b, 0xda, 0x21, 0x6e, 0xc3, 0x59, 0x22, 0x1d, 0x6b, 0x69,
+	0xf7, 0x02, 0x69, 0x75, 0x9a, 0xe4, 0xc2, 0x52, 0x83, 0xda, 0xd4, 0x25, 0x1e, 0xad, 0x97, 0x3a,
+	0xae, 0xe3, 0x39, 0xe8, 0xf9, 0x70, 0x54, 0x49, 0x8e, 0x2a, 0x89, 0x51, 0x25, 0xd2, 0xb1, 0x4a,
+	0xfe, 0xa8, 0xf9, 0x57, 0x35, 0xec, 0x86, 0xd3, 0x70, 0x96, 0xc4, 0xe0, 0xad, 0xee, 0xb6, 0xf8,
+	0x4f, 0xfc, 0x23, 0xfe, 0x92, 0xa0, 0xf3, 0xb7, 0x76, 0x2e, 0xb3, 0x92, 0x25, 0x38, 0xd3, 0x87,
+	0x1e, 0xb5, 0x99, 0xe5, 0xd8, 0xec, 0x55, 0xd2, 0xb1, 0x18, 0x75, 0x77, 0xa9, 0xbb, 0xd4, 0xd9,
+	0x69, 0x70, 0x1a, 0x8b, 0x76, 0x58, 0xda, 0xed, 0x99, 0xde, 0xfc, 0x6b, 0x21, 0x52, 0x9b, 0xd4,
+	0x9a, 0x96, 0x4d, 0xdd, 0xbd, 0x70, 0x78, 0x9b, 0x7a, 0x24, 0x69, 0xd4, 0x52, 0xbf, 0x51, 0x6e,
+	0xd7, 0xf6, 0xac, 0x36, 0xed, 0x19, 0xf0, 0xfa, 0x61, 0x03, 0x58, 0xad, 0x49, 0xdb, 0x24, 0x3e,
+	0xce, 0xfc, 0x00, 0xce, 0xae, 0xd8, 0xa4, 0xb5, 0xc7, 0x2c, 0x86, 0xbb, 0xf6, 0x8a, 0xdb, 0xe8,
+	0xb6, 0xa9, 0xed, 0xa1, 0xf3, 0x30, 0x62, 0x93, 0x36, 0x9d, 0x33, 0xce, 0x1b, 0x2f, 0x16, 0xca,
+	0xe3, 0x9f, 0xef, 0x2f, 0x9e, 0x39, 0xd8, 0x5f, 0x1c, 0x79, 0x9b, 0xb4, 0x29, 0x16, 0x14, 0xf4,
+	0x1c, 0xe4, 0x76, 0x49, 0xab, 0x4b, 0xe7, 0x32, 0xa2, 0xcb, 0x84, 0xea, 0x92, 0xbb, 0xcb, 0x1b,
+	0xb1, 0xa4, 0x99, 0xbf, 0x9b, 0x8d, 0xc0, 0x7f, 0x93, 0x7a, 0xa4, 0x4e, 0x3c, 0x82, 0xda, 0x90,
+	0x6f, 0x91, 0x2d, 0xda, 0x62, 0x73, 0xc6, 0xf9, 0xec, 0x8b, 0xc5, 0xe5, 0xeb, 0xa5, 0x41, 0x36,
+	0xb1, 0x94, 0x00, 0x55, 0x5a, 0x17, 0x38, 0xd7, 0x6d, 0xcf, 0xdd, 0x2b, 0x4f, 0xaa, 0x49, 0xe4,
+	0x65, 0x23, 0x56, 0x4c, 0xd0, 0x6f, 0x1b, 0x50, 0x24, 0xb6, 0xed, 0x78, 0xc4, 0xe3, 0xdb, 0x34,
+	0x97, 0x11, 0x4c, 0x6f, 0x0f, 0xcf, 0x74, 0x25, 0x04, 0x93, 0x9c, 0xcf, 0x2a, 0xce, 0x45, 0x8d,
+	0x82, 0x75, 0x9e, 0xf3, 0x6f, 0x42, 0x51, 0x9b, 0x2a, 0x9a, 0x86, 0xec, 0x0e, 0xdd, 0x93, 0xf2,
+	0xc5, 0xfc, 0x4f, 0x34, 0x1b, 0x11, 0xa8, 0x92, 0xe0, 0x95, 0xcc, 0x65, 0x63, 0xfe, 0x1a, 0x4c,
+	0xc7, 0x19, 0xa6, 0x19, 0x6f, 0xfe, 0x89, 0x01, 0xb3, 0xda, 0x2a, 0x30, 0xdd, 0xa6, 0x2e, 0xb5,
+	0x6b, 0x14, 0x2d, 0x41, 0x81, 0xef, 0x25, 0xeb, 0x90, 0x9a, 0xbf, 0xd5, 0x33, 0x6a, 0x21, 0x85,
+	0xb7, 0x7d, 0x02, 0x0e, 0xfb, 0x04, 0xc7, 0x22, 0xf3, 0xb8, 0x63, 0xd1, 0x69, 0x12, 0x46, 0xe7,
+	0xb2, 0xd1, 0x63, 0xb1, 0xc1, 0x1b, 0xb1, 0xa4, 0x99, 0x0f, 0xe0, 0x6b, 0xfe, 0x7c, 0x36, 0x69,
+	0xbb, 0xd3, 0x22, 0x1e, 0x0d, 0x27, 0x75, 0xf8, 0xd1, 0x3b, 0x0f, 0x23, 0x3b, 0x96, 0x5d, 0x8f,
+	0xcf, 0xe2, 0x1b, 0x96, 0x5d, 0xc7, 0x82, 0x62, 0xee, 0xc0, 0xc4, 0x4a, 0xa7, 0xe3, 0x3a, 0xbb,
+	0xb4, 0x5e, 0xf5, 0x48, 0x83, 0xa2, 0x7b, 0x00, 0x44, 0x35, 0xac, 0x78, 0x02, 0xba, 0xb8, 0xfc,
+	0x8b, 0x25, 0xa9, 0x33, 0x25, 0x5d, 0x67, 0x4a, 0x9d, 0x9d, 0x06, 0x6f, 0x60, 0x25, 0xae, 0x9a,
+	0xa5, 0xdd, 0x0b, 0xa5, 0x4d, 0xab, 0x4d, 0xcb, 0x93, 0x07, 0xfb, 0x8b, 0xb0, 0x12, 0x20, 0x60,
+	0x0d, 0xcd, 0xfc, 0x1d, 0x03, 0xce, 0xad, 0xb8, 0x0d, 0xa7, 0xb2, 0xba, 0xd2, 0xe9, 0xdc, 0xa2,
+	0xa4, 0xe5, 0x35, 0xab, 0x1e, 0xf1, 0xba, 0x0c, 0x5d, 0x83, 0x3c, 0x13, 0x7f, 0xa9, 0xc5, 0xbc,
+	0xe0, 0x9f, 0x4f, 0x49, 0x7f, 0xb4, 0xbf, 0x38, 0x9b, 0x30, 0x90, 0x62, 0x35, 0x0a, 0xbd, 0x04,
+	0xa3, 0x6d, 0xca, 0x18, 0x69, 0xf8, 0x12, 0x9f, 0x52, 0x00, 0xa3, 0xdf, 0x94, 0xcd, 0xd8, 0xa7,
+	0x9b, 0xff, 0x9c, 0x81, 0xa9, 0x00, 0x4b, 0xb1, 0x3f, 0x81, 0xed, 0xed, 0xc2, 0x78, 0x53, 0x5b,
+	0xa1, 0xd8, 0xe5, 0xe2, 0xf2, 0xd5, 0x01, 0x35, 0x29, 0x49, 0x48, 0xe5, 0x59, 0xc5, 0x66, 0x5c,
+	0x6f, 0xc5, 0x11, 0x36, 0xa8, 0x0d, 0xc0, 0xf6, 0xec, 0x9a, 0x62, 0x3a, 0x22, 0x98, 0xbe, 0x99,
+	0x92, 0x69, 0x35, 0x00, 0x28, 0x23, 0xc5, 0x12, 0xc2, 0x36, 0xac, 0x31, 0x30, 0xff, 0xce, 0x80,
+	0xb3, 0x09, 0xe3, 0xd0, 0x5b, 0xb1, 0xfd, 0x7c, 0xbe, 0x67, 0x3f, 0x51, 0xcf, 0xb0, 0x70, 0x37,
+	0x5f, 0x81, 0x31, 0x97, 0xee, 0x5a, 0xfc, 0xa6, 0x50, 0x12, 0x9e, 0x56, 0xe3, 0xc7, 0xb0, 0x6a,
+	0xc7, 0x41, 0x0f, 0xf4, 0x32, 0x14, 0xfc, 0xbf, 0xb9, 0x98, 0xb3, 0x5c, 0x99, 0xf8, 0xc6, 0xf9,
+	0x5d, 0x19, 0x0e, 0xe9, 0xe6, 0x6f, 0x41, 0xae, 0xd2, 0x24, 0xae, 0xc7, 0x4f, 0x8c, 0x4b, 0x3b,
+	0xce, 0x3b, 0x78, 0x5d, 0x4d, 0x31, 0x38, 0x31, 0x58, 0x36, 0x63, 0x9f, 0x3e, 0xc0, 0x66, 0xbf,
+	0x04, 0xa3, 0xbb, 0xd4, 0x15, 0xf3, 0xcd, 0x46, 0xc1, 0xee, 0xca, 0x66, 0xec, 0xd3, 0xcd, 0x7f,
+	0x37, 0x60, 0x56, 0xcc, 0x60, 0xd5, 0x62, 0x35, 0x67, 0x97, 0xba, 0x7b, 0x98, 0xb2, 0x6e, 0xeb,
+	0x98, 0x27, 0xb4, 0x0a, 0xd3, 0x8c, 0xb6, 0x77, 0xa9, 0x5b, 0x71, 0x6c, 0xe6, 0xb9, 0xc4, 0xb2,
+	0x3d, 0x35, 0xb3, 0x39, 0xd5, 0x7b, 0xba, 0x1a, 0xa3, 0xe3, 0x9e, 0x11, 0xe8, 0x45, 0x18, 0x53,
+	0xd3, 0xe6, 0x47, 0x89, 0x0b, 0x76, 0x9c, 0xef, 0x81, 0x5a, 0x13, 0xc3, 0x01, 0xd5, 0xfc, 0x6f,
+	0x03, 0x66, 0xc4, 0xaa, 0xaa, 0xdd, 0x2d, 0x56, 0x73, 0xad, 0x0e, 0x37, 0xc0, 0x4f, 0xe2, 0x92,
+	0xae, 0xc1, 0x64, 0xdd, 0x17, 0xfc, 0xba, 0xd5, 0xb6, 0x3c, 0xa1, 0x23, 0xb9, 0xf2, 0x53, 0x0a,
+	0x63, 0x72, 0x35, 0x42, 0xc5, 0xb1, 0xde, 0x72, 0xfb, 0x5a, 0x5d, 0xe6, 0x51, 0x77, 0xc3, 0x75,
+	0xda, 0x0e, 0x5f, 0xe7, 0x26, 0x61, 0x3b, 0xe8, 0x57, 0x61, 0xac, 0xad, 0x2e, 0x3d, 0x65, 0x35,
+	0x7f, 0x69, 0x30, 0xab, 0x79, 0x67, 0xeb, 0xdb, 0xb4, 0xe6, 0xf1, 0x0b, 0x33, 0xd4, 0xb6, 0xb0,
+	0x0d, 0x07, 0xa8, 0xe8, 0x3d, 0x18, 0x61, 0x1d, 0x5a, 0x13, 0x22, 0x2a, 0x2e, 0xbf, 0x31, 0x98,
+	0x52, 0x47, 0x26, 0x59, 0xed, 0xd0, 0x5a, 0x28, 0x5b, 0xfe, 0x1f, 0x16, 0x90, 0xe6, 0x7f, 0x1a,
+	0x30, 0x97, 0xb4, 0xaa, 0x75, 0x8b, 0x79, 0xe8, 0x83, 0x9e, 0x95, 0x95, 0x06, 0x5b, 0x19, 0x1f,
+	0x2d, 0xd6, 0x15, 0x68, 0xaf, 0xdf, 0xa2, 0xad, 0xea, 0x01, 0xe4, 0x2c, 0x8f, 0xb6, 0x7d, 0x57,
+	0xe3, 0xca, 0x60, 0xcb, 0x4a, 0x9a, 0x6c, 0x78, 0x85, 0xae, 0x71, 0x40, 0x2c, 0x71, 0xcd, 0xf7,
+	0x61, 0xbc, 0xd2, 0x75, 0x5d, 0x6a, 0x7b, 0xf2, 0x82, 0xfb, 0x06, 0xe4, 0x98, 0x65, 0x2b, 0x3b,
+	0x9f, 0xee, 0x6e, 0x2b, 0x70, 0xf0, 0x2a, 0x1f, 0x8c, 0x25, 0x86, 0xf9, 0x17, 0x59, 0x38, 0xeb,
+	0x9f, 0x18, 0x5a, 0x5f, 0x71, 0x3d, 0x6b, 0x9b, 0xd4, 0x3c, 0x86, 0xea, 0x30, 0x5e, 0x0f, 0x9b,
+	0x3d, 0x65, 0x88, 0xd3, 0xf0, 0x0a, 0x8c, 0xbd, 0x06, 0xef, 0xe1, 0x08, 0x2a, 0x7a, 0x17, 0xb2,
+	0x0d, 0xcb, 0x53, 0x9e, 0xe1, 0xe5, 0xc1, 0x24, 0x77, 0xd3, 0x8a, 0x5b, 0x9e, 0x72, 0x51, 0xb1,
+	0xca, 0xde, 0xb4, 0x3c, 0xcc, 0x11, 0xd1, 0x16, 0xe4, 0xad, 0x36, 0x69, 0xd0, 0x94, 0xbb, 0xb2,
+	0xc6, 0xc7, 0xc4, 0xd1, 0x03, 0x57, 0x53, 0x50, 0x19, 0x56, 0xc8, 0x9c, 0x47, 0x8d, 0x5b, 0x0c,
+	0x69, 0xb3, 0x07, 0xdf, 0xf9, 0x04, 0xdb, 0x19, 0xf2, 0x10, 0x54, 0x86, 0x15, 0xb2, 0xf9, 0x65,
+	0x06, 0xa6, 0x43, 0xf9, 0x55, 0x9c, 0x76, 0xdb, 0xf2, 0xd0, 0x3c, 0x64, 0xac, 0xba, 0x32, 0x48,
+	0xa0, 0x06, 0x66, 0xd6, 0x56, 0x71, 0xc6, 0xaa, 0xa3, 0x17, 0x20, 0xbf, 0xe5, 0x12, 0xbb, 0xd6,
+	0x54, 0x86, 0x28, 0x00, 0x2e, 0x8b, 0x56, 0xac, 0xa8, 0xe8, 0x59, 0xc8, 0x7a, 0xa4, 0xa1, 0xec,
+	0x4f, 0x20, 0xbf, 0x4d, 0xd2, 0xc0, 0xbc, 0x9d, 0x1b, 0x3e, 0xd6, 0x15, 0x3a, 0x2c, 0x76, 0x5e,
+	0x33, 0x7c, 0x55, 0xd9, 0x8c, 0x7d, 0x3a, 0xe7, 0x48, 0xba, 0x5e, 0xd3, 0x71, 0xe7, 0x72, 0x51,
+	0x8e, 0x2b, 0xa2, 0x15, 0x2b, 0x2a, 0x77, 0x51, 0x6a, 0x62, 0xfe, 0x1e, 0x75, 0xe7, 0xf2, 0x51,
+	0x17, 0xa5, 0xe2, 0x13, 0x70, 0xd8, 0x07, 0xdd, 0x87, 0x62, 0xcd, 0xa5, 0xc4, 0x73, 0xdc, 0x55,
+	0xe2, 0xd1, 0xb9, 0xd1, 0xd4, 0x27, 0x70, 0x8a, 0x7b, 0xe9, 0x95, 0x10, 0x02, 0xeb, 0x78, 0xe6,
+	0xdf, 0x67, 0x61, 0x2e, 0x14, 0xad, 0xd8, 0xdb, 0xd0, 0x33, 0x55, 0xe2, 0x31, 0xfa, 0x88, 0xe7,
+	0x05, 0xc8, 0xd7, 0xad, 0x06, 0x65, 0x5e, 0x5c, 0xca, 0xab, 0xa2, 0x15, 0x2b, 0x2a, 0xfa, 0x83,
+	0x58, 0x34, 0x92, 0x13, 0x07, 0xe5, 0xce, 0x60, 0x07, 0xa5, 0xdf, 0xe4, 0x86, 0x08, 0x49, 0xd0,
+	0x32, 0x40, 0xc3, 0xf2, 0xd4, 0xa5, 0xa5, 0x76, 0x3d, 0x30, 0xd6, 0x37, 0x03, 0x0a, 0xd6, 0x7a,
+	0xa1, 0x77, 0xa1, 0x20, 0xe4, 0x35, 0xa4, 0xfe, 0x0b, 0x17, 0xa6, 0xe2, 0x03, 0xe0, 0x10, 0xeb,
+	0xc8, 0x41, 0xce, 0xfb, 0x80, 0xae, 0x3f, 0xec, 0xb8, 0x94, 0xf1, 0xab, 0xfb, 0x2e, 0x71, 0x2d,
+	0xb2, 0xd5, 0xa2, 0xc7, 0x15, 0xc7, 0x7e, 0x31, 0x02, 0xa3, 0x37, 0x5c, 0x6a, 0x35, 0x9a, 0xde,
+	0x29, 0x5c, 0x89, 0xcf, 0x41, 0x8e, 0xb4, 0x2c, 0xc2, 0xc4, 0xe9, 0xd6, 0xa6, 0xb4, 0xc2, 0x1b,
+	0xb1, 0xa4, 0xa1, 0xf7, 0x21, 0xef, 0xb8, 0x56, 0xc3, 0xb2, 0xe7, 0x0a, 0x62, 0x12, 0x17, 0x07,
+	0x3b, 0x3f, 0x6a, 0x15, 0x77, 0xc4, 0xd0, 0xf0, 0x88, 0xca, 0xff, 0xb1, 0x82, 0x44, 0xf7, 0x60,
+	0x54, 0xaa, 0x9c, 0x6f, 0xc6, 0x96, 0x06, 0x36, 0xc3, 0x52, 0x6b, 0x43, 0xd3, 0x20, 0xff, 0x67,
+	0xd8, 0x07, 0x44, 0xd5, 0xc0, 0x0a, 0x8f, 0x08, 0xe8, 0x97, 0x53, 0x58, 0xe1, 0xbe, 0x66, 0xb7,
+	0x1a, 0x98, 0xdd, 0x5c, 0x1a, 0x50, 0x61, 0x58, 0xfb, 0xd9, 0x59, 0x2e, 0x62, 0xe5, 0xee, 0xe7,
+	0x87, 0x10, 0xb1, 0x8a, 0x35, 0x26, 0xa3, 0x31, 0x82, 0x1f, 0x0d, 0x98, 0xdf, 0xc9, 0xc2, 0x8c,
+	0xea, 0x59, 0x71, 0x5a, 0x2d, 0x5a, 0x13, 0xbe, 0xa5, 0xb4, 0xe2, 0xd9, 0x44, 0x2b, 0x6e, 0xf9,
+	0x3e, 0x85, 0xbc, 0x19, 0xcb, 0xa9, 0x66, 0x13, 0xf2, 0x28, 0x09, 0x3f, 0x42, 0xda, 0x88, 0x60,
+	0x97, 0x54, 0x2f, 0xe5, 0x5d, 0xa0, 0xdf, 0x37, 0xe0, 0xec, 0x2e, 0x75, 0xad, 0x6d, 0xab, 0x26,
+	0xf4, 0xf1, 0x96, 0xc5, 0x3c, 0xc7, 0xdd, 0x53, 0xf7, 0xe6, 0xeb, 0x83, 0x71, 0xbe, 0xab, 0x01,
+	0xac, 0xd9, 0xdb, 0x4e, 0xf9, 0x19, 0xc5, 0xed, 0xec, 0xdd, 0x5e, 0x68, 0x9c, 0xc4, 0x6f, 0xbe,
+	0x03, 0x10, 0xce, 0x36, 0xc1, 0x1c, 0xac, 0xeb, 0xca, 0x3b, 0xf0, 0xc4, 0xfc, 0xc5, 0xfa, 0xb6,
+	0x53, 0x37, 0x23, 0x9f, 0x19, 0x50, 0x54, 0xf4, 0x53, 0x70, 0x13, 0x71, 0xd4, 0x4d, 0x7c, 0x35,
+	0xd5, 0xfc, 0xfb, 0x78, 0x86, 0x2e, 0x4c, 0x44, 0x94, 0x1c, 0x5d, 0x52, 0xe9, 0x12, 0x69, 0x03,
+	0x7f, 0x5e, 0x4f, 0x97, 0x3c, 0xda, 0x5f, 0x9c, 0x89, 0x74, 0x0e, 0x73, 0x28, 0x87, 0xc7, 0x2e,
+	0x57, 0xc6, 0xbe, 0xfb, 0xd7, 0x8b, 0x67, 0x3e, 0xfe, 0xd1, 0xf9, 0x33, 0xe6, 0xa7, 0x59, 0x98,
+	0x8e, 0x4b, 0x75, 0x00, 0xdb, 0x1b, 0xda, 0xb0, 0xb1, 0x13, 0xb5, 0x61, 0x99, 0x93, 0xb3, 0x61,
+	0xd9, 0x93, 0xb0, 0x61, 0x23, 0xc7, 0x66, 0xc3, 0xcc, 0x7f, 0x33, 0x60, 0x32, 0xd8, 0x99, 0x0f,
+	0xbb, 0xdc, 0xff, 0x08, 0xa5, 0x6e, 0x1c, 0xbf, 0xd4, 0x1f, 0xc0, 0x28, 0x73, 0xba, 0x6e, 0x4d,
+	0x38, 0xd9, 0x1c, 0xfd, 0xb5, 0x74, 0x46, 0x53, 0x8e, 0xd5, 0x3c, 0x4b, 0xd9, 0x80, 0x7d, 0x54,
+	0xf3, 0xb3, 0x4c, 0xb0, 0x20, 0x45, 0x93, 0x8e, 0x97, 0xcb, 0xdd, 0x52, 0xbe, 0xa0, 0x31, 0xdd,
+	0xf1, 0xe2, 0xad, 0x58, 0x51, 0x91, 0x29, 0xec, 0xb9, 0xef, 0xff, 0x17, 0xca, 0xa0, 0xcc, 0xb2,
+	0xd8, 0x04, 0x49, 0x41, 0x1d, 0x98, 0x76, 0xe9, 0x87, 0x5d, 0xcb, 0xa5, 0xf5, 0xaa, 0x43, 0x76,
+	0xb8, 0xd3, 0xa2, 0x92, 0x5c, 0x03, 0xea, 0xfd, 0x6a, 0xd7, 0x15, 0x26, 0xac, 0x3c, 0xcb, 0x63,
+	0x77, 0x1c, 0xc3, 0xc2, 0x3d, 0xe8, 0xc8, 0x81, 0x59, 0xb2, 0x4b, 0xac, 0x16, 0xd9, 0xb2, 0x5a,
+	0x96, 0xb7, 0x57, 0xf5, 0x5c, 0xe2, 0xd1, 0xc6, 0x9e, 0x72, 0xb1, 0xaf, 0xaa, 0xb5, 0xcc, 0xae,
+	0x24, 0xf4, 0x79, 0xb4, 0xbf, 0xf8, 0x8c, 0x92, 0x45, 0x12, 0x19, 0x27, 0x02, 0x9b, 0xff, 0x95,
+	0x0b, 0x2c, 0x84, 0xca, 0x6b, 0xfd, 0x3a, 0x14, 0x6b, 0x32, 0x98, 0x6c, 0xed, 0xad, 0xd9, 0xea,
+	0x4c, 0xaf, 0x0e, 0x71, 0xdb, 0x95, 0x2a, 0x21, 0x4c, 0xcc, 0x0b, 0xd5, 0x28, 0x58, 0xe7, 0x86,
+	0x3e, 0x02, 0x90, 0xa6, 0x9f, 0xd6, 0xd7, 0x6c, 0x75, 0xb7, 0x55, 0x86, 0xe1, 0x7d, 0x37, 0x40,
+	0x91, 0xac, 0x03, 0x27, 0x2b, 0x24, 0x60, 0x8d, 0x15, 0x5f, 0xb5, 0x9f, 0xc5, 0xbd, 0xe1, 0xb8,
+	0xca, 0x48, 0x0c, 0xb5, 0xea, 0x95, 0x10, 0x26, 0xee, 0x7b, 0x87, 0x14, 0xac, 0x73, 0x9b, 0x77,
+	0x61, 0x3a, 0x2e, 0xab, 0x84, 0xfb, 0xed, 0x56, 0xf4, 0x7e, 0x5b, 0x1e, 0xd0, 0x22, 0x68, 0x89,
+	0x01, 0xfd, 0x1d, 0xc1, 0x85, 0xa9, 0x98, 0x8c, 0x12, 0x58, 0xae, 0x45, 0x59, 0x5e, 0x4c, 0x73,
+	0xd7, 0xab, 0x6c, 0xbb, 0xce, 0x93, 0xc1, 0x74, 0x5c, 0x3a, 0xc7, 0xc6, 0x34, 0x92, 0xe2, 0xd7,
+	0x2f, 0xf1, 0xbf, 0xcc, 0x40, 0x21, 0x30, 0xe3, 0x69, 0xf2, 0x75, 0xd2, 0xfd, 0xca, 0x1c, 0x12,
+	0x44, 0x67, 0x07, 0x09, 0xa2, 0x47, 0xfa, 0x07, 0xd1, 0x7e, 0x4e, 0x3f, 0xff, 0xf8, 0x9c, 0xbe,
+	0x16, 0x44, 0x8f, 0x0e, 0x1e, 0x44, 0x8f, 0x1d, 0x1e, 0x44, 0x9b, 0x7f, 0x63, 0x00, 0xea, 0xcd,
+	0x98, 0xa4, 0x11, 0x14, 0x89, 0x5f, 0xae, 0xaf, 0xa7, 0x0d, 0x5f, 0x0f, 0xbb, 0x63, 0xcd, 0xcf,
+	0x72, 0x30, 0x75, 0xd3, 0x1a, 0x3a, 0xf5, 0xea, 0xc1, 0xd3, 0x12, 0xa9, 0x4a, 0x95, 0xe3, 0x1b,
+	0x58, 0x56, 0xb9, 0xbf, 0x57, 0xd4, 0xd0, 0xa7, 0x2b, 0xc9, 0xdd, 0x1e, 0xf5, 0x27, 0xe1, 0x7e,
+	0xd0, 0x03, 0x1f, 0x92, 0xab, 0x30, 0xc1, 0x3c, 0xd7, 0xaa, 0x79, 0x32, 0xb9, 0xcb, 0xe6, 0x8a,
+	0xe2, 0xe6, 0x3a, 0xa7, 0xba, 0x4f, 0x54, 0x75, 0x22, 0x8e, 0xf6, 0x4d, 0xcc, 0x19, 0x8f, 0xa4,
+	0xce, 0x19, 0x2f, 0x41, 0x81, 0xb4, 0x5a, 0xce, 0x47, 0x9b, 0xa4, 0xc1, 0x54, 0x96, 0x26, 0x38,
+	0x35, 0x2b, 0x3e, 0x01, 0x87, 0x7d, 0x50, 0x09, 0xc0, 0x6a, 0xd8, 0x8e, 0x4b, 0xc5, 0x88, 0xbc,
+	0xb8, 0x42, 0xc5, 0xbb, 0xd8, 0x5a, 0xd0, 0x8a, 0xb5, 0x1e, 0xa8, 0x0a, 0xe7, 0x2c, 0x9b, 0xd1,
+	0x5a, 0xd7, 0xa5, 0xd5, 0x1d, 0xab, 0xb3, 0xb9, 0x5e, 0x15, 0x56, 0x62, 0x4f, 0x9c, 0xe6, 0xb1,
+	0xf2, 0xb3, 0x8a, 0xd9, 0xb9, 0xb5, 0xa4, 0x4e, 0x38, 0x79, 0x2c, 0x7a, 0x0d, 0xc6, 0x2d, 0xbb,
+	0xd6, 0xea, 0xd6, 0xe9, 0x06, 0xf1, 0x9a, 0x6c, 0x6e, 0x4c, 0x4c, 0x63, 0xfa, 0x60, 0x7f, 0x71,
+	0x7c, 0x4d, 0x6b, 0xc7, 0x91, 0x5e, 0x7c, 0x14, 0x7d, 0xa8, 0x8d, 0x2a, 0x84, 0xa3, 0xae, 0x3f,
+	0xd4, 0x47, 0xe9, 0xbd, 0x12, 0xb2, 0xea, 0x90, 0x2a, 0xab, 0xfe, 0xfd, 0x0c, 0xe4, 0xe5, 0xa3,
+	0x16, 0xba, 0x14, 0x7b, 0x39, 0x7a, 0xb6, 0xe7, 0xe5, 0xa8, 0x98, 0xf4, 0x00, 0x68, 0x42, 0xde,
+	0x62, 0xac, 0x1b, 0xf5, 0x58, 0xd6, 0x44, 0x0b, 0x56, 0x14, 0x91, 0x71, 0x74, 0xec, 0x6d, 0xab,
+	0xa1, 0xd2, 0x31, 0xd7, 0x34, 0x3f, 0x25, 0x2c, 0x4d, 0x78, 0x10, 0xd4, 0x2e, 0x84, 0x2e, 0x4b,
+	0xa4, 0x03, 0xf7, 0x5d, 0x6e, 0x57, 0xef, 0xbc, 0x2d, 0x79, 0x54, 0x04, 0x22, 0x56, 0xc8, 0x9c,
+	0x87, 0xd3, 0xf5, 0x3a, 0x5d, 0x4f, 0x1c, 0x94, 0x63, 0xe2, 0x71, 0x47, 0x20, 0x62, 0x85, 0x6c,
+	0x7e, 0x6a, 0xc0, 0x94, 0x94, 0x41, 0xa5, 0x49, 0x6b, 0x3b, 0x55, 0x8f, 0x76, 0x78, 0x08, 0xd1,
+	0x65, 0x94, 0xc5, 0x43, 0x88, 0x77, 0x18, 0x65, 0x58, 0x50, 0xb4, 0xd5, 0x67, 0x4e, 0x6a, 0xf5,
+	0xe6, 0x65, 0xd0, 0x36, 0x47, 0xbc, 0xca, 0xca, 0xc7, 0x49, 0x79, 0x85, 0x65, 0x43, 0x23, 0x24,
+	0x7b, 0xed, 0x61, 0x9f, 0x6e, 0x1e, 0x64, 0x20, 0x27, 0xbc, 0xfc, 0x34, 0x96, 0x2b, 0x9a, 0x96,
+	0xcb, 0x0c, 0x94, 0x96, 0x3b, 0x24, 0x73, 0x1b, 0xa6, 0x26, 0x47, 0x1e, 0x9b, 0x9a, 0x64, 0x49,
+	0x99, 0xc9, 0xb7, 0x52, 0x04, 0x37, 0xc3, 0x54, 0x46, 0x1c, 0x35, 0xf3, 0xf7, 0x13, 0x03, 0x66,
+	0x93, 0x72, 0xf4, 0x69, 0x64, 0xfe, 0x0a, 0x8c, 0x75, 0x5a, 0xc4, 0xdb, 0x76, 0xdc, 0x76, 0xfc,
+	0x6d, 0x76, 0x43, 0xb5, 0xe3, 0xa0, 0x07, 0x72, 0x01, 0x5c, 0x3f, 0xcc, 0xf5, 0x43, 0xc0, 0x6b,
+	0x47, 0xcb, 0xdf, 0x86, 0x3b, 0x1c, 0x34, 0x31, 0xac, 0x71, 0x31, 0xff, 0x28, 0x07, 0x33, 0x62,
+	0xc8, 0xb0, 0x17, 0xe2, 0x30, 0xc7, 0xaa, 0x03, 0x4f, 0x89, 0xe0, 0xb4, 0xf7, 0x0e, 0x95, 0x27,
+	0xed, 0xb2, 0x1a, 0xff, 0xd4, 0x5a, 0x62, 0xaf, 0x47, 0x7d, 0x29, 0xb8, 0x0f, 0x6e, 0xef, 0xc5,
+	0x08, 0x3f, 0x7b, 0x17, 0xa3, 0x7e, 0xd8, 0x46, 0x0f, 0x3d, 0x6c, 0x7d, 0xaf, 0xd1, 0xb1, 0x23,
+	0x5c, 0xa3, 0xbd, 0x57, 0x5b, 0x21, 0xd5, 0xd5, 0xf6, 0x57, 0x19, 0x18, 0xdd, 0x70, 0x1d, 0xf1,
+	0xd6, 0x73, 0xf2, 0x09, 0xf1, 0x77, 0x86, 0x7c, 0x23, 0xe6, 0x50, 0xd2, 0x96, 0x8b, 0x37, 0xe2,
+	0xb1, 0xe8, 0xfb, 0xb0, 0x96, 0xdf, 0xcd, 0xa6, 0x09, 0x3b, 0x14, 0xf0, 0x21, 0xf9, 0xdd, 0x1f,
+	0x18, 0x30, 0x11, 0x99, 0xc2, 0x13, 0xfc, 0x96, 0x1e, 0x93, 0x53, 0xd2, 0x5b, 0xfa, 0xbf, 0x18,
+	0x30, 0x13, 0xe9, 0x79, 0x0a, 0xd9, 0xd1, 0x6f, 0x45, 0xb3, 0xa3, 0x17, 0x87, 0x58, 0x4f, 0x9f,
+	0x1c, 0xe9, 0x9f, 0xc5, 0x57, 0xc3, 0x57, 0x8a, 0x7e, 0x13, 0x66, 0x3a, 0xfe, 0xd3, 0xfb, 0x86,
+	0xd3, 0xb2, 0x6a, 0x16, 0xf5, 0x93, 0xed, 0x97, 0x52, 0xd6, 0x25, 0x88, 0xe1, 0x7b, 0xe5, 0xaf,
+	0x29, 0xee, 0x33, 0x1b, 0x71, 0x5c, 0xdc, 0xcb, 0x4a, 0xe4, 0x9e, 0xd5, 0xac, 0x9e, 0xd8, 0xdc,
+	0xb3, 0x9a, 0x5f, 0x1f, 0xb9, 0xfe, 0xd0, 0x80, 0x71, 0x4d, 0x3d, 0x18, 0x6a, 0x02, 0x7c, 0x44,
+	0x5c, 0xda, 0x74, 0x02, 0x37, 0x6e, 0xe0, 0x8c, 0xe0, 0xbb, 0xfe, 0x38, 0x81, 0x14, 0x9e, 0xfc,
+	0xa0, 0x9d, 0x61, 0x0d, 0x1b, 0x7d, 0x4b, 0x4b, 0xee, 0x49, 0xdd, 0x1a, 0x88, 0x8b, 0xc8, 0x1d,
+	0x48, 0x0e, 0xba, 0x26, 0x6b, 0x29, 0x41, 0xf3, 0x7b, 0x99, 0x40, 0x93, 0x55, 0xbe, 0xac, 0x06,
+	0x50, 0x73, 0xec, 0xba, 0xe5, 0x05, 0xc5, 0x59, 0xc5, 0xe5, 0xa5, 0xc1, 0xb6, 0xa6, 0xe2, 0x8f,
+	0x0b, 0x17, 0x14, 0x34, 0x31, 0xac, 0xc1, 0xa2, 0x8b, 0x7e, 0x25, 0x65, 0x34, 0x62, 0x90, 0x95,
+	0x94, 0x8f, 0xf6, 0x17, 0x7d, 0x41, 0xeb, 0x95, 0x95, 0x29, 0x2a, 0x06, 0x51, 0x15, 0x72, 0xdc,
+	0x54, 0xf9, 0xe5, 0x74, 0xcb, 0xa9, 0x8d, 0x1f, 0x53, 0x95, 0x23, 0xfc, 0x4f, 0x2c, 0xb1, 0xcc,
+	0xbf, 0xcd, 0x40, 0x21, 0x38, 0xeb, 0xa7, 0x7e, 0x33, 0x5c, 0x4c, 0xa9, 0xa5, 0xfd, 0xac, 0x1d,
+	0xba, 0x1f, 0xbb, 0x19, 0xd2, 0xaa, 0xff, 0x21, 0x77, 0xc3, 0x3f, 0xca, 0xbb, 0x41, 0xf6, 0x3d,
+	0x05, 0x55, 0xdf, 0x8c, 0xaa, 0xfa, 0x52, 0xca, 0xd5, 0xf4, 0x51, 0xf6, 0x8f, 0x33, 0x30, 0x15,
+	0x33, 0x78, 0xe8, 0x39, 0x71, 0xa8, 0x1a, 0xfe, 0xa1, 0x0d, 0x06, 0xaa, 0x14, 0x9d, 0xa0, 0xa1,
+	0x5d, 0xee, 0xe3, 0x05, 0xde, 0x9f, 0xe3, 0x2a, 0x21, 0x7f, 0x7d, 0x28, 0x1b, 0xeb, 0x83, 0x94,
+	0x67, 0xa4, 0x7b, 0xa8, 0xe1, 0xe2, 0x28, 0x1b, 0xb4, 0x01, 0xb3, 0xa4, 0xeb, 0x39, 0x01, 0xc0,
+	0x75, 0x9b, 0x6c, 0xb5, 0xa8, 0xcc, 0xf7, 0x8d, 0x95, 0x7f, 0x2e, 0xc8, 0xb4, 0x27, 0xf4, 0xc1,
+	0x89, 0x23, 0xcd, 0xef, 0x19, 0xf0, 0x74, 0x9f, 0xf9, 0x0c, 0xf0, 0xfc, 0xd5, 0x82, 0x09, 0x51,
+	0xa0, 0x1e, 0xc8, 0xc1, 0x3f, 0xc5, 0x83, 0xed, 0xbc, 0x3e, 0x54, 0xae, 0x3e, 0xd2, 0x84, 0xa3,
+	0xe0, 0xe6, 0x0f, 0x32, 0x80, 0x82, 0xb9, 0xa6, 0x79, 0xa5, 0xbb, 0x0f, 0xa3, 0xdb, 0x32, 0xdd,
+	0x7d, 0xb4, 0x67, 0xd6, 0x72, 0x51, 0x7f, 0x69, 0xf6, 0x31, 0xd1, 0x7b, 0xc7, 0xa3, 0x6b, 0xd0,
+	0xab, 0x67, 0xe8, 0x1e, 0xc0, 0xb6, 0x65, 0x5b, 0xac, 0x39, 0x64, 0xb5, 0x8a, 0x70, 0xe2, 0x6f,
+	0x04, 0x08, 0x58, 0x43, 0x33, 0xff, 0x3c, 0xa3, 0xe9, 0xb0, 0x70, 0x1f, 0x06, 0x3a, 0xfb, 0x2f,
+	0x45, 0x85, 0x59, 0xe8, 0x7d, 0x82, 0x0f, 0x04, 0x73, 0x0f, 0x46, 0x76, 0x89, 0xeb, 0xbf, 0x06,
+	0x0e, 0x58, 0x08, 0xd7, 0x5b, 0x03, 0x13, 0xee, 0xe9, 0x5d, 0xe2, 0x32, 0x2c, 0x30, 0xb9, 0x6b,
+	0xc5, 0x3c, 0xda, 0xf1, 0x2f, 0xaf, 0xd4, 0x86, 0xd3, 0xa3, 0x1d, 0x7d, 0x81, 0xb4, 0x23, 0x6e,
+	0x00, 0xda, 0x61, 0xe6, 0x77, 0x46, 0x35, 0xab, 0xa0, 0xee, 0xcb, 0xdb, 0x80, 0x5a, 0x84, 0x79,
+	0xb7, 0x88, 0x5d, 0xe7, 0xba, 0x44, 0xb7, 0x5d, 0xca, 0x9a, 0x2a, 0x32, 0x9b, 0x57, 0x28, 0x68,
+	0xbd, 0xa7, 0x07, 0x4e, 0x18, 0x85, 0x2e, 0x45, 0xaf, 0xc5, 0xc5, 0xf8, 0xb5, 0x38, 0x19, 0xea,
+	0xe3, 0x90, 0x17, 0xa3, 0x76, 0xde, 0x73, 0x27, 0x70, 0xde, 0x7f, 0x03, 0x66, 0xb6, 0xe3, 0x35,
+	0x19, 0xaa, 0x8e, 0xed, 0x8d, 0x21, 0x4b, 0x3a, 0xca, 0xe7, 0x0e, 0xc2, 0x87, 0xfc, 0xb0, 0x19,
+	0xf7, 0x32, 0x42, 0x8e, 0x5f, 0xc0, 0x2f, 0x92, 0x6c, 0x32, 0x7f, 0x3a, 0xb0, 0xce, 0xc5, 0xd2,
+	0x73, 0xf1, 0xd2, 0x7d, 0x09, 0x89, 0x23, 0x0c, 0x62, 0x3a, 0x98, 0x3f, 0x4e, 0x1d, 0x44, 0x97,
+	0x82, 0x77, 0x4b, 0x3e, 0x1d, 0x11, 0xc2, 0x66, 0x7b, 0x5e, 0x1c, 0x39, 0x09, 0xeb, 0xfd, 0xd0,
+	0x27, 0x06, 0x9c, 0xe3, 0x87, 0xf5, 0xfa, 0x43, 0x5a, 0xeb, 0x72, 0xa9, 0xf8, 0xdf, 0xf5, 0xcc,
+	0x15, 0x85, 0x34, 0xae, 0x0e, 0xea, 0x3a, 0x26, 0x40, 0x84, 0xf1, 0x78, 0x22, 0x19, 0x27, 0x33,
+	0x46, 0x0f, 0xa4, 0x33, 0x46, 0x45, 0xba, 0xe3, 0xe8, 0x59, 0xcc, 0xc0, 0x31, 0x93, 0x76, 0xc7,
+	0xa3, 0xe6, 0x8f, 0xb3, 0xba, 0xb9, 0x1a, 0x2c, 0xb7, 0x7a, 0x0f, 0x46, 0x3c, 0xc2, 0x76, 0x94,
+	0x16, 0xbc, 0x35, 0x44, 0x69, 0x76, 0xa8, 0x0b, 0x22, 0xf6, 0x16, 0x4d, 0x02, 0x13, 0xcd, 0x43,
+	0x86, 0xb0, 0xf8, 0x4b, 0xdb, 0x0a, 0xc3, 0x19, 0xc2, 0xc4, 0x2b, 0xdc, 0xb6, 0xca, 0x8c, 0x84,
+	0xaf, 0x70, 0xdb, 0x38, 0x63, 0x6d, 0xa3, 0xf7, 0x20, 0xe7, 0x52, 0xcf, 0xdd, 0x53, 0xd6, 0xfc,
+	0xf2, 0x10, 0x86, 0x0b, 0xf3, 0xf1, 0x52, 0x44, 0xe2, 0x4f, 0x2c, 0x11, 0x03, 0x7b, 0x9b, 0x3f,
+	0x01, 0x7b, 0x1b, 0xa6, 0xa9, 0xb3, 0x27, 0x96, 0xa6, 0xfe, 0xbe, 0xa1, 0x5d, 0xf0, 0xc1, 0x42,
+	0xd1, 0x3b, 0x30, 0xea, 0x59, 0x6d, 0xea, 0x74, 0xbd, 0x74, 0x9e, 0x65, 0x50, 0xc8, 0x20, 0xcc,
+	0xd8, 0xa6, 0x84, 0xc0, 0x3e, 0x16, 0xba, 0x06, 0x93, 0xd4, 0x75, 0x1d, 0x77, 0xb3, 0xc9, 0xcd,
+	0xb2, 0xd3, 0x92, 0x6e, 0xd4, 0x44, 0x98, 0x41, 0xba, 0x1e, 0xa1, 0xe2, 0x58, 0x6f, 0x3f, 0x3f,
+	0xf2, 0xb3, 0xf2, 0xad, 0x81, 0xca, 0x8f, 0x9c, 0xea, 0x47, 0x06, 0x43, 0xe7, 0x47, 0x0e, 0xfd,
+	0xba, 0xe0, 0x03, 0x78, 0x2a, 0x59, 0x8f, 0x8f, 0xe5, 0xeb, 0xbc, 0x7f, 0x8a, 0xcb, 0x4a, 0xb8,
+	0x4f, 0xbe, 0xfa, 0x19, 0x27, 0xe9, 0xee, 0x64, 0x8e, 0xdb, 0xdd, 0x71, 0xf5, 0xa5, 0xa8, 0x6f,
+	0x19, 0xd1, 0x7d, 0x75, 0xce, 0x8c, 0x34, 0x5f, 0xc7, 0xf5, 0xc0, 0xf4, 0x3d, 0x6b, 0xff, 0x6a,
+	0xc0, 0xb9, 0xc4, 0xde, 0x81, 0x0c, 0x33, 0x27, 0x29, 0x43, 0xe3, 0xb8, 0x65, 0xf8, 0xdd, 0x0c,
+	0x4c, 0x63, 0xda, 0x71, 0x22, 0x2f, 0x1b, 0x1b, 0xfe, 0x57, 0x20, 0x29, 0x62, 0x82, 0x58, 0xb9,
+	0x40, 0x79, 0x34, 0xf2, 0xf9, 0x07, 0x57, 0x97, 0xb6, 0xef, 0x00, 0x0e, 0xac, 0xfe, 0x3d, 0x6f,
+	0x2e, 0xf2, 0xe6, 0x90, 0xaf, 0x37, 0x12, 0x90, 0x23, 0x8b, 0x72, 0x3b, 0x65, 0xdc, 0xdf, 0x48,
+	0x51, 0xb8, 0xd7, 0x8b, 0x2c, 0x9a, 0xb1, 0x04, 0x34, 0x3f, 0xcd, 0x80, 0x8c, 0x1f, 0x4e, 0xc1,
+	0x3a, 0xfe, 0x4a, 0xc4, 0x3a, 0x2e, 0xa5, 0xc9, 0x9f, 0xf5, 0xcb, 0xa3, 0xc4, 0x63, 0xbb, 0x0b,
+	0x29, 0x93, 0x72, 0x8f, 0xc9, 0xa1, 0xfc, 0x83, 0x01, 0x05, 0xd1, 0xef, 0x14, 0x0c, 0xed, 0x46,
+	0xd4, 0xd0, 0xbe, 0x9c, 0x62, 0x15, 0x7d, 0x0c, 0xec, 0xff, 0x64, 0xd5, 0xec, 0x83, 0xc8, 0xb1,
+	0x49, 0xdc, 0xba, 0x0a, 0x89, 0x42, 0x2d, 0xe1, 0x8d, 0x58, 0xd2, 0x02, 0xdd, 0x1e, 0x3d, 0x01,
+	0xdd, 0xfe, 0x35, 0x59, 0xf5, 0x48, 0x99, 0x47, 0xeb, 0x37, 0x82, 0xd8, 0x27, 0x9b, 0xba, 0x7c,
+	0x53, 0x95, 0x98, 0x86, 0xcf, 0x6d, 0x38, 0x86, 0x8a, 0x7b, 0xf8, 0xf0, 0x78, 0xa8, 0x13, 0x37,
+	0x66, 0x2a, 0x4e, 0x78, 0x63, 0x48, 0xcb, 0x29, 0xe3, 0xa1, 0x9e, 0x66, 0xdc, 0xcb, 0x08, 0x35,
+	0x61, 0x5c, 0x2f, 0x3c, 0x57, 0xe7, 0x74, 0x39, 0x7d, 0x85, 0xbb, 0xac, 0x26, 0xd1, 0x5b, 0x70,
+	0x04, 0xd9, 0xfc, 0x63, 0x03, 0x20, 0xcc, 0x36, 0xf3, 0x3d, 0xaf, 0x39, 0x5d, 0x5b, 0xa6, 0x01,
+	0xb2, 0xe1, 0x9e, 0x57, 0x78, 0x23, 0x96, 0x34, 0xae, 0x3f, 0x32, 0x98, 0x52, 0x87, 0xfa, 0x42,
+	0x9a, 0x38, 0x2d, 0x96, 0xd5, 0x96, 0x8d, 0x58, 0x01, 0x9a, 0xfb, 0x79, 0x28, 0x6a, 0x7a, 0x16,
+	0xcb, 0x69, 0x4f, 0x9c, 0x4c, 0x4e, 0x3b, 0x39, 0x11, 0x50, 0x1c, 0x2a, 0x11, 0x70, 0x21, 0x9a,
+	0x08, 0x78, 0x26, 0x9e, 0x08, 0x90, 0xc2, 0x8e, 0x24, 0x01, 0x18, 0x4c, 0xaa, 0x88, 0xd8, 0xff,
+	0xa0, 0x41, 0xe6, 0x56, 0x86, 0x8e, 0xbb, 0x11, 0x77, 0x74, 0x6f, 0x44, 0x20, 0x71, 0x8c, 0x05,
+	0x77, 0x94, 0x55, 0x4b, 0xb5, 0xdb, 0x6e, 0x13, 0x77, 0x6f, 0x6e, 0x5c, 0x4c, 0x38, 0x70, 0x94,
+	0x6f, 0x44, 0xa8, 0x38, 0xd6, 0x1b, 0x6d, 0x04, 0x67, 0x40, 0x16, 0xc9, 0xbf, 0x92, 0xe6, 0x0c,
+	0xc8, 0x40, 0x21, 0xba, 0xf5, 0x7a, 0x2e, 0xa4, 0x70, 0x48, 0x2e, 0xe4, 0x36, 0x20, 0x67, 0x4b,
+	0x84, 0x24, 0xf5, 0x9b, 0xf2, 0x97, 0x43, 0xb8, 0x92, 0xe4, 0xc5, 0x91, 0x0d, 0x36, 0xec, 0x4e,
+	0x4f, 0x0f, 0x9c, 0x30, 0x8a, 0x1b, 0x19, 0x15, 0x85, 0x07, 0x9a, 0xa9, 0xf2, 0x1e, 0x69, 0xa3,
+	0xb8, 0x30, 0xac, 0x14, 0x45, 0xd6, 0x95, 0x18, 0x2a, 0xee, 0xe1, 0x83, 0x3e, 0x84, 0x09, 0x7e,
+	0x84, 0x42, 0xc6, 0x70, 0x44, 0xc6, 0x2a, 0xdf, 0xaa, 0x41, 0xe2, 0x28, 0x07, 0xf3, 0x4f, 0xb3,
+	0x90, 0x9c, 0x03, 0x08, 0xbf, 0xef, 0x32, 0x1e, 0xf3, 0x7d, 0xd7, 0xbb, 0x50, 0x60, 0x1e, 0x71,
+	0xe5, 0x87, 0x76, 0x99, 0xe1, 0x3e, 0xb4, 0xab, 0xfa, 0x00, 0x38, 0xc4, 0x8a, 0x25, 0x64, 0xb2,
+	0xc7, 0x9a, 0x90, 0x59, 0x06, 0x10, 0x61, 0x9e, 0x30, 0x62, 0xe2, 0x36, 0x9b, 0x08, 0x6d, 0xc2,
+	0xf5, 0x80, 0x82, 0xb5, 0x5e, 0xe8, 0xeb, 0x81, 0x8f, 0x20, 0x6b, 0x2d, 0x7e, 0xa1, 0xa7, 0x34,
+	0xee, 0x6c, 0xc4, 0x89, 0x8c, 0xe5, 0x78, 0x07, 0xaf, 0xa7, 0x35, 0xff, 0x2f, 0x03, 0x11, 0x03,
+	0x8d, 0xfe, 0xd0, 0x80, 0x19, 0x12, 0xfb, 0x21, 0x12, 0xdf, 0xbf, 0xfd, 0xe5, 0x74, 0xbf, 0x0e,
+	0xd3, 0xf3, 0x3b, 0x26, 0xe1, 0xdb, 0x6f, 0xbc, 0x0b, 0xc3, 0xbd, 0x4c, 0xd1, 0xef, 0x19, 0x70,
+	0x96, 0xf4, 0xfe, 0xd2, 0x8c, 0xda, 0xf9, 0x37, 0x87, 0xfe, 0xa9, 0x9a, 0xf2, 0xd3, 0x07, 0xfb,
+	0x8b, 0x49, 0xbf, 0xc1, 0x83, 0x93, 0xd8, 0xa1, 0xf7, 0x61, 0x84, 0xb8, 0x0d, 0x3f, 0x2d, 0x9c,
+	0x9e, 0xad, 0xff, 0x03, 0x42, 0xa1, 0x97, 0xb1, 0xe2, 0x36, 0x18, 0x16, 0xa0, 0xe6, 0x8f, 0xb2,
+	0x30, 0x1d, 0xff, 0x28, 0x4c, 0x95, 0x5c, 0x8f, 0x24, 0x96, 0x5c, 0x73, 0x45, 0x11, 0x0f, 0x23,
+	0xf1, 0x0f, 0x21, 0xc5, 0xfb, 0x86, 0xa4, 0x05, 0x8a, 0x22, 0x3e, 0xd5, 0xc8, 0x1d, 0x41, 0x51,
+	0xc4, 0xf7, 0x19, 0x21, 0x16, 0xba, 0x1c, 0xbd, 0x60, 0xcc, 0xf8, 0x05, 0x33, 0xa3, 0xaf, 0x65,
+	0xd8, 0x64, 0x73, 0x1b, 0x8a, 0xda, 0x3e, 0x28, 0x75, 0xbc, 0x92, 0x5a, 0xee, 0xe1, 0xb1, 0x9b,
+	0x92, 0xb5, 0x76, 0x21, 0x45, 0xc7, 0x0f, 0x95, 0x5f, 0x48, 0xeb, 0x48, 0xd9, 0x58, 0x21, 0x2e,
+	0x0d, 0xcd, 0xfc, 0x0f, 0x03, 0x26, 0x22, 0xdf, 0x01, 0x70, 0x6e, 0xfe, 0xf7, 0x16, 0xc3, 0xff,
+	0xea, 0xce, 0xdd, 0x00, 0x01, 0x6b, 0x68, 0xe8, 0xdb, 0x50, 0x6c, 0x39, 0x76, 0x83, 0x32, 0xaf,
+	0xea, 0x90, 0x1d, 0xa5, 0x27, 0x69, 0x53, 0x5b, 0x73, 0x07, 0xfb, 0x8b, 0xb3, 0xeb, 0x12, 0xa6,
+	0xe2, 0xb4, 0x3b, 0x2d, 0xea, 0xc9, 0x2f, 0x73, 0xb0, 0x0e, 0x2e, 0x5e, 0xb5, 0x83, 0xb2, 0x83,
+	0x27, 0xf5, 0x55, 0x3b, 0xac, 0x97, 0x38, 0xe6, 0x57, 0xed, 0x48, 0x21, 0xc6, 0x21, 0xaf, 0xda,
+	0x41, 0xdf, 0x27, 0xf6, 0x55, 0x3b, 0x98, 0x61, 0x9f, 0xc8, 0xec, 0x7f, 0x33, 0xda, 0x2a, 0xa2,
+	0xd1, 0x59, 0xe6, 0x31, 0xd1, 0xd9, 0x07, 0x30, 0x66, 0xd9, 0x1e, 0x75, 0x77, 0x49, 0x4b, 0xa5,
+	0xa6, 0xd3, 0x9e, 0xc5, 0x60, 0xa9, 0x6b, 0x0a, 0x07, 0x07, 0x88, 0xa8, 0x05, 0xe7, 0xfc, 0xa7,
+	0x1c, 0x97, 0x92, 0xf0, 0xb1, 0x59, 0x95, 0x61, 0xbe, 0xee, 0xbf, 0x39, 0xdc, 0x48, 0xea, 0xf4,
+	0xa8, 0x1f, 0x01, 0x27, 0x83, 0x22, 0x06, 0x13, 0x4c, 0x4b, 0x4b, 0xf8, 0x37, 0xe2, 0x80, 0xcf,
+	0x60, 0xf1, 0x4c, 0x8e, 0x56, 0xbb, 0xa9, 0x83, 0xe2, 0x28, 0x0f, 0xf3, 0x13, 0x03, 0x26, 0xa3,
+	0x25, 0x3f, 0x3f, 0xf5, 0x10, 0xe9, 0x87, 0x59, 0x98, 0x8a, 0x1d, 0xfe, 0x58, 0x98, 0x54, 0x38,
+	0xcd, 0x30, 0x29, 0x3f, 0x54, 0x98, 0x94, 0xec, 0xc1, 0x8f, 0x0c, 0xe5, 0xc1, 0x5f, 0x95, 0x5e,
+	0xb4, 0x3a, 0x4c, 0x6b, 0xab, 0xea, 0x63, 0xa3, 0x60, 0x83, 0xd7, 0x75, 0x22, 0x8e, 0xf6, 0x15,
+	0x1e, 0x4e, 0xbd, 0xf7, 0x47, 0x65, 0x54, 0x08, 0xf0, 0x66, 0xda, 0xf2, 0xe9, 0x00, 0x40, 0x7a,
+	0x38, 0x09, 0x04, 0x9c, 0xc4, 0xae, 0x7c, 0xfb, 0xf3, 0xaf, 0x16, 0xce, 0x7c, 0xf1, 0xd5, 0xc2,
+	0x99, 0x2f, 0xbf, 0x5a, 0x38, 0xf3, 0xf1, 0xc1, 0x82, 0xf1, 0xf9, 0xc1, 0x82, 0xf1, 0xc5, 0xc1,
+	0x82, 0xf1, 0xe5, 0xc1, 0x82, 0xf1, 0xe3, 0x83, 0x05, 0xe3, 0x93, 0x9f, 0x2c, 0x9c, 0xb9, 0xf7,
+	0xfc, 0x20, 0x3f, 0x45, 0xf9, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0xee, 0x12, 0xe4, 0x1c, 0xb1,
+	0x52, 0x00, 0x00,
 }
 
 func (m *AnalysisRunArgument) Marshal() (dAtA []byte, err error) {
@@ -20009,7 +20009,7 @@ func (m *StepExecutionMetadata) Unmarshal(dAtA []byte) error {
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Status = PromotionPhase(dAtA[iNdEx:postIndex])
+			m.Status = PromotionStepStatus(dAtA[iNdEx:postIndex])
 			iNdEx = postIndex
 		case 6:
 			if wireType != 2 {
diff --git a/api/v1alpha1/promotion_types.go b/api/v1alpha1/promotion_types.go
index 1f8ba97e..8bcf2b14 100644
--- a/api/v1alpha1/promotion_types.go
+++ b/api/v1alpha1/promotion_types.go
@@ -25,7 +25,8 @@ const (
 	// PromotionPhaseSucceeded denotes a Promotion that has been successfully
 	// executed.
 	PromotionPhaseSucceeded PromotionPhase = "Succeeded"
-	// PromotionPhaseFailed denotes a Promotion that has failed
+	// PromotionPhaseFailed denotes a Promotion that has failed, usually for
+	// non-technical reasons.
 	PromotionPhaseFailed PromotionPhase = "Failed"
 	// PromotionPhaseErrored denotes a Promotion that has failed for technical
 	// reasons. Further information about the failure can be found in the
@@ -36,6 +37,33 @@ const (
 	PromotionPhaseAborted PromotionPhase = "Aborted"
 )
 
+type PromotionStepStatus string
+
+const (
+	// PromotionStepStatusRunning denotes a PromotionStep that is currently
+	// "running." This does not necessarily indicate that the step is ACTIVELY
+	// running, but rather that it has not yet completed. It may, for instance, be
+	// waiting on some external event to occur and will check again on the next
+	// reconciliation attempt.
+	PromotionStepStatusRunning PromotionStepStatus = "Running"
+	// PromotionStepStatusSucceeded denotes a PromotionStep that has completed
+	// successfully.
+	PromotionStepStatusSucceeded PromotionStepStatus = "Succeeded"
+	// PromotionStepStatusFailed denotes a PromotionStep that has failed, usually
+	// for non-technical reasons.
+	PromotionStepStatusFailed PromotionStepStatus = "Failed"
+	// PromotionStepStatusErrored denotes a Promotion that has failed for technical
+	// reasons. Further information about the failure can be found in the
+	// Promotion's status.
+	PromotionStepStatusErrored PromotionStepStatus = "Errored"
+	// PromotionStepStatusAborted denotes a PromotionStep that was aborted because
+	// the Promotion to which it belongs was aborted.
+	PromotionStepStatusAborted PromotionStepStatus = "Aborted"
+	// PromotionStepStatusSkipped denotes a PromotionStep that was skipped for any
+	// reason and immediately yielded execution to the next PromotionStep.
+	PromotionStepStatusSkipped PromotionStepStatus = "Skipped"
+)
+
 // IsTerminal returns true if the PromotionPhase is a terminal one.
 func (p *PromotionPhase) IsTerminal() bool {
 	switch *p {
@@ -335,7 +363,7 @@ type StepExecutionMetadata struct {
 	// ErrorCount tracks consecutive failed attempts to execute the step.
 	ErrorCount uint32 `json:"errorCount,omitempty" protobuf:"varint,4,opt,name=errorCount"`
 	// Status is the high-level outcome of the step.
-	Status PromotionPhase `json:"status,omitempty" protobuf:"bytes,5,opt,name=status"`
+	Status PromotionStepStatus `json:"status,omitempty" protobuf:"bytes,5,opt,name=status"`
 	// Message is a display message about the step, including any errors.
 	Message string `json:"message,omitempty" protobuf:"bytes,6,opt,name=message"`
 }
diff --git a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-clone.md b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-clone.md
index ef836de0..477367e1 100644
--- a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-clone.md
+++ b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-clone.md
@@ -24,6 +24,10 @@ multiple working trees.
 |------|------|----------|-------------|
 | `repoURL` | `string` | Y | The URL of a remote Git repository to clone. |
 | `insecureSkipTLSVerify` | `boolean` | N | Whether to bypass TLS certificate verification when cloning (and for all subsequent operations involving this clone). Setting this to `true` is highly discouraged in production. |
+| `author` | `[]object` | N | Default authorship information for any commits made to the cloned repository. If provided, this overrides any system-level defaults. Note: Configuration of the [`git-commit`](./git-commit.md) step can override this information. |
+| `author.name` | `string` | Y | The committer's name. |
+| `author.email` | `string` | Y | The committer's email address. |
+| `author.signingKey` | `string` | N | The GPG signing key for the author. This field is optional. |
 | `checkout` | `[]object` | Y | The commits, branches, or tags to check out from the repository and the paths where they should be checked out. At least one must be specified. |
 | `checkout[].branch` | `string` | N | A branch to check out. Mutually exclusive with `commit` and `tag`. If none of these is specified, the default branch will be checked out. |
 | `checkout[].create` | `boolean` | N | In the event `branch` does not already exist on the remote, whether a new, empty, orphaned branch should be created. Default is `false`, but should commonly be set to `true` for Stage-specific branches, which may not exist yet at the time of a Stage's first promotion. |
diff --git a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-commit.md b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-commit.md
index 1ba88fc0..abcb5d42 100644
--- a/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-commit.md
+++ b/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-commit.md
@@ -15,9 +15,10 @@ desired state and is commonly followed by a [`git-push` step](git-push.md).
 |------|------|----------|-------------|
 | `path` | `string` | Y | Path to a Git working tree containing changes to be committed. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
 | `message` | `string` | Y | The commit message. |
-| `author` | `[]object` | N | Optionally provider authorship information for the commit. |
-| `author.name` | `string` | N | The committer's name. |
-| `author.email` | `string` | N | The committer's email address. |
+| `author` | `[]object` | N | Optional authorship information for the commit. If provided, this takes precedence over both system-level defaults and any default authorship information configured in the [`git-clone`](./git-clone.md) step. |
+| `author.name` | `string` | Y | The committer's name. |
+| `author.email` | `string` | Y | The committer's email address. |
+| `author.signingKey` | `string` | N | The GPG signing key for the author. This field is optional. |
 
 ## Output
 
diff --git a/internal/controller/git/work_tree.go b/internal/controller/git/work_tree.go
index e08d04d1..d8b1aa4d 100644
--- a/internal/controller/git/work_tree.go
+++ b/internal/controller/git/work_tree.go
@@ -12,8 +12,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/google/uuid"
-
 	libExec "github.com/akuity/kargo/internal/exec"
 	"github.com/akuity/kargo/internal/logging"
 )
@@ -214,21 +212,20 @@ func (w *workTree) Commit(message string, opts *CommitOptions) error {
 		// repository-level author information by creating a temporary home
 		// directory, configuring the author information "globally" within it, and
 		// then ensuring the git commit command uses that home directory.
-		homeDir = filepath.Join(w.homeDir, uuid.New().String())
-		if err := os.MkdirAll(homeDir, 0700); err != nil {
+		var err error
+		if homeDir, err = os.MkdirTemp(w.homeDir, ""); err != nil {
 			return fmt.Errorf(
 				"error creating virtual home directory %q for commit command: %w",
 				homeDir, err,
 			)
 		}
 		defer func() {
-			if err := os.RemoveAll(homeDir); err != nil {
+			if cleanErr := os.RemoveAll(homeDir); cleanErr != nil {
 				logging.LoggerFromContext(context.TODO()).
-					Error(err, "error removing virtual home directory", "path", homeDir)
+					Error(cleanErr, "error removing virtual home directory", "path", homeDir)
 			}
 		}()
-		err := w.setupAuthor(homeDir, opts.Author)
-		if err != nil {
+		if err = w.setupAuthor(homeDir, opts.Author); err != nil {
 			return fmt.Errorf(
 				"error setting up author information for commit command: %w", err,
 			)
diff --git a/internal/controller/promotions/promotions.go b/internal/controller/promotions/promotions.go
index 733e6021..c16c6778 100644
--- a/internal/controller/promotions/promotions.go
+++ b/internal/controller/promotions/promotions.go
@@ -619,13 +619,24 @@ func (r *reconciler) terminatePromotion(
 	}
 
 	newStatus := promo.Status.DeepCopy()
+
+	now := &metav1.Time{Time: time.Now()}
+
+	// If a step was running, mark the step as aborted.
+	if newStatus.Phase == kargoapi.PromotionPhaseRunning &&
+		int64(len(newStatus.StepExecutionMetadata)) == promo.Status.CurrentStep+1 &&
+		promo.Status.StepExecutionMetadata[promo.Status.CurrentStep].Status == kargoapi.PromotionStepStatusRunning {
+		newStatus.StepExecutionMetadata[promo.Status.CurrentStep].Status = kargoapi.PromotionStepStatusAborted
+		newStatus.StepExecutionMetadata[promo.Status.CurrentStep].FinishedAt = now
+	}
+
 	newStatus.Phase = kargoapi.PromotionPhaseAborted
 	if actor != "" {
 		newStatus.Message = fmt.Sprintf("Promotion terminated by %s", actor)
 	} else {
 		newStatus.Message = "Promotion terminated per user request"
 	}
-	newStatus.FinishedAt = &metav1.Time{Time: time.Now()}
+	newStatus.FinishedAt = now
 
 	if err := kubeclient.PatchStatus(ctx, r.kargoClient, promo, func(status *kargoapi.PromotionStatus) {
 		*status = *newStatus
diff --git a/internal/promotion/runner/builtin/argocd_updater.go b/internal/promotion/runner/builtin/argocd_updater.go
index d3d65304..ac1837f4 100644
--- a/internal/promotion/runner/builtin/argocd_updater.go
+++ b/internal/promotion/runner/builtin/argocd_updater.go
@@ -124,11 +124,11 @@ func (a *argocdUpdater) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := a.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.ArgoCDUpdateConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", a.Name(), err)
 	}
 	return a.run(ctx, stepCtx, cfg)
@@ -146,7 +146,7 @@ func (a *argocdUpdater) run(
 	stepCfg builtin.ArgoCDUpdateConfig,
 ) (promotion.StepResult, error) {
 	if a.argocdClient == nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, errors.New(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, errors.New(
 			"Argo CD integration is disabled on this controller; cannot update " +
 				"Argo CD Application resources",
 		)
@@ -169,7 +169,7 @@ func (a *argocdUpdater) run(
 		}
 		app, err := a.getAuthorizedApplicationFn(ctx, stepCtx, appKey)
 		if err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 				"error getting Argo CD Application %q in namespace %q: %w",
 				appKey.Name, appKey.Namespace, err,
 			)
@@ -197,7 +197,7 @@ func (a *argocdUpdater) run(
 				if phase == "" {
 					// If we do not have a phase, we cannot continue processing
 					// this update by waiting.
-					return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+					return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 				}
 				// Log the error as a warning, but continue to the next update.
 				logger.Info(err.Error())
@@ -205,7 +205,7 @@ func (a *argocdUpdater) run(
 			if phase.Failed() {
 				// Record the reason for the failure if available.
 				if app.Status.OperationState != nil {
-					return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+					return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 						"Argo CD Application %q in namespace %q failed with: %s",
 						app.Name,
 						app.Namespace,
@@ -214,7 +214,7 @@ func (a *argocdUpdater) run(
 				}
 				// If the update failed, we can short-circuit. This is
 				// effectively "fail fast" behavior.
-				return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, nil
+				return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, nil
 			}
 			// If we get here, we can continue to the next update.
 			continue
@@ -233,7 +233,7 @@ func (a *argocdUpdater) run(
 			app,
 		)
 		if err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 				"error building desired sources for Argo CD Application %q in namespace %q: %w",
 				app.Name, app.Namespace, err,
 			)
@@ -246,7 +246,7 @@ func (a *argocdUpdater) run(
 			app,
 			desiredSources,
 		); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 				"error syncing Argo CD Application %q in namespace %q: %w",
 				app.Name, app.Namespace, err,
 			)
@@ -255,9 +255,9 @@ func (a *argocdUpdater) run(
 		updateResults = append(updateResults, argocd.OperationRunning)
 	}
 
-	aggregatedStatus := a.operationPhaseToPromotionStatus(updateResults...)
+	aggregatedStatus := a.operationPhaseToPromotionStepStatus(updateResults...)
 	if aggregatedStatus == "" {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"could not determine promotion step status from operation phases: %v",
 			updateResults,
 		)
@@ -794,9 +794,9 @@ func (a *argocdUpdater) buildHelmParamChangesForAppSource(
 	return changes
 }
 
-func (a *argocdUpdater) operationPhaseToPromotionStatus(
+func (a *argocdUpdater) operationPhaseToPromotionStepStatus(
 	phases ...argocd.OperationPhase,
-) kargoapi.PromotionPhase {
+) kargoapi.PromotionStepStatus {
 	if len(phases) == 0 {
 		return ""
 	}
@@ -805,11 +805,11 @@ func (a *argocdUpdater) operationPhaseToPromotionStatus(
 
 	switch phases[0] {
 	case argocd.OperationRunning, argocd.OperationTerminating:
-		return kargoapi.PromotionPhaseRunning
+		return kargoapi.PromotionStepStatusRunning
 	case argocd.OperationFailed, argocd.OperationError:
-		return kargoapi.PromotionPhaseErrored
+		return kargoapi.PromotionStepStatusErrored
 	case argocd.OperationSucceeded:
-		return kargoapi.PromotionPhaseSucceeded
+		return kargoapi.PromotionStepStatusSucceeded
 	default:
 		return ""
 	}
diff --git a/internal/promotion/runner/builtin/file_copier.go b/internal/promotion/runner/builtin/file_copier.go
index 4dbd2edd..20bb1edc 100644
--- a/internal/promotion/runner/builtin/file_copier.go
+++ b/internal/promotion/runner/builtin/file_copier.go
@@ -51,13 +51,13 @@ func (f *fileCopier) Run(
 ) (promotion.StepResult, error) {
 	// Validate the configuration against the JSON Schema.
 	if err := validate(f.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), f.Name()); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Convert the configuration into a typed object.
 	cfg, err := promotion.ConfigToStruct[builtin.CopyConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", f.Name(), err)
 	}
 
@@ -72,19 +72,19 @@ func (f *fileCopier) run(
 	// Secure join the paths to prevent path traversal attacks.
 	inPath, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.InPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not secure join inPath %q: %w", cfg.InPath, err)
 	}
 	outPath, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.OutPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not secure join outPath %q: %w", cfg.OutPath, err)
 	}
 
 	// Load the ignore rules.
 	matcher, err := f.loadIgnoreRules(inPath, cfg.Ignore)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to load ignore rules: %w", err)
 	}
 
@@ -102,10 +102,10 @@ func (f *fileCopier) run(
 		},
 	}
 	if err = copy.Copy(inPath, outPath, opts); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to copy %q to %q: %w", cfg.InPath, cfg.OutPath, err)
 	}
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
 
 // loadIgnoreRules loads the ignore rules from the given string. The rules are
diff --git a/internal/promotion/runner/builtin/file_deleter.go b/internal/promotion/runner/builtin/file_deleter.go
index 229eb9b8..298a02d4 100644
--- a/internal/promotion/runner/builtin/file_deleter.go
+++ b/internal/promotion/runner/builtin/file_deleter.go
@@ -42,13 +42,13 @@ func (f *fileDeleter) Run(
 ) (promotion.StepResult, error) {
 	// Validate the configuration against the JSON Schema.
 	if err := validate(f.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), f.Name()); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Convert the configuration into a typed object.
 	cfg, err := promotion.ConfigToStruct[builtin.DeleteConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", f.Name(), err)
 	}
 
@@ -62,24 +62,24 @@ func (f *fileDeleter) run(
 ) (promotion.StepResult, error) {
 	absPath, err := f.resolveAbsPath(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not secure join path %q: %w", cfg.Path, err)
 	}
 
 	symlink, err := f.isSymlink(absPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	if symlink {
 		if f.ignoreNotExist(cfg.Strict, os.Remove(absPath)) != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 		}
 	} else {
 		// Secure join the paths to prevent path traversal.
 		pathToDelete, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
 		if err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("could not secure join path %q: %w", cfg.Path, err)
 		}
 
@@ -87,12 +87,12 @@ func (f *fileDeleter) run(
 			cfg.Strict,
 			removePath(pathToDelete),
 		); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("failed to delete %q: %w", cfg.Path, sanitizePathError(err, stepCtx.WorkDir))
 		}
 	}
 
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
 
 // isSymlink checks if a path is a symlink.
diff --git a/internal/promotion/runner/builtin/git_cloner.go b/internal/promotion/runner/builtin/git_cloner.go
index 6e386141..ebbcfa86 100644
--- a/internal/promotion/runner/builtin/git_cloner.go
+++ b/internal/promotion/runner/builtin/git_cloner.go
@@ -65,11 +65,11 @@ func (g *gitCloner) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.GitCloneConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", g.Name(), err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -93,7 +93,7 @@ func (g *gitCloner) run(
 		cfg.RepoURL,
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting credentials for %s: %w", cfg.RepoURL, err)
 	}
 	if creds != nil {
@@ -103,10 +103,22 @@ func (g *gitCloner) run(
 			SSHPrivateKey: creds.SSHPrivateKey,
 		}
 	}
+
+	var repoUser git.User
+	if cfg.Author != nil {
+		repoUser = git.User{
+			Name:  cfg.Author.Name,
+			Email: cfg.Author.Email,
+			SigningKey:     cfg.Author.SigningKey, // Optional, may be empty
+		}
+	} else {
+		repoUser = g.gitUser // Default to the system-level gitUser
+	}
+
 	repo, err := git.CloneBare(
 		cfg.RepoURL,
 		&git.ClientOptions{
-			User:                  &g.gitUser,
+			User:                  &repoUser,
 			Credentials:           repoCreds,
 			InsecureSkipTLSVerify: cfg.InsecureSkipTLSVerify,
 		},
@@ -115,7 +127,7 @@ func (g *gitCloner) run(
 		},
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error cloning %s: %w", cfg.RepoURL, err)
 	}
 	for _, checkout := range cfg.Checkout {
@@ -124,7 +136,7 @@ func (g *gitCloner) run(
 		case checkout.Branch != "":
 			ref = checkout.Branch
 			if err = ensureRemoteBranch(repo, ref, checkout.Create); err != nil {
-				return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+				return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 					fmt.Errorf("error ensuring existence of remote branch %s: %w", ref, err)
 			}
 		case checkout.Commit != "":
@@ -134,7 +146,7 @@ func (g *gitCloner) run(
 		}
 		path, err := securejoin.SecureJoin(stepCtx.WorkDir, checkout.Path)
 		if err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 				"error joining path %s with work dir %s: %w",
 				checkout.Path, stepCtx.WorkDir, err,
 			)
@@ -143,7 +155,7 @@ func (g *gitCloner) run(
 			path,
 			&git.AddWorkTreeOptions{Ref: ref},
 		); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 				"error adding work tree %s to repo %s: %w",
 				checkout.Path, cfg.RepoURL, err,
 			)
@@ -152,7 +164,7 @@ func (g *gitCloner) run(
 	// Note: We do NOT defer repo.Close() because we want to keep the repository
 	// around on the FS for subsequent promotion steps to use. The Engine will
 	// handle all work dir cleanup.
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
 
 // ensureRemoteBranch checks for the existence of a remote branch. If the remote
diff --git a/internal/promotion/runner/builtin/git_commiter.go b/internal/promotion/runner/builtin/git_commiter.go
index d5b907b8..8c22bf12 100644
--- a/internal/promotion/runner/builtin/git_commiter.go
+++ b/internal/promotion/runner/builtin/git_commiter.go
@@ -41,11 +41,11 @@ func (g *gitCommitter) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.GitCommitConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", g.Name(), err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -63,48 +63,46 @@ func (g *gitCommitter) run(
 ) (promotion.StepResult, error) {
 	path, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"error joining path %s with work dir %s: %w",
 			cfg.Path, stepCtx.WorkDir, err,
 		)
 	}
 	workTree, err := git.LoadWorkTree(path, nil)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error loading working tree from %s: %w", cfg.Path, err)
 	}
 	if err = workTree.AddAll(); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error adding all changes to working tree: %w", err)
 	}
 	hasDiffs, err := workTree.HasDiffs()
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error checking for diffs in working tree: %w", err)
 	}
 	if hasDiffs {
 		commitOpts := &git.CommitOptions{}
 		if cfg.Author != nil {
-			commitOpts.Author = &git.User{}
-			if cfg.Author.Name != "" {
-				commitOpts.Author.Name = cfg.Author.Name
-			}
-			if cfg.Author.Email != "" {
-				commitOpts.Author.Email = cfg.Author.Email
+			commitOpts.Author = &git.User{
+				Name:       cfg.Author.Name,
+				Email:      cfg.Author.Email,
+				SigningKey: cfg.Author.SigningKey,
 			}
 		}
 		if err = workTree.Commit(cfg.Message, commitOpts); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("error committing to working tree: %w", err)
 		}
 	}
 	commitID, err := workTree.LastCommitID()
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting last commit ID: %w", err)
 	}
 	return promotion.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: map[string]any{stateKeyCommit: commitID},
 	}, nil
 }
diff --git a/internal/promotion/runner/builtin/git_pr_opener.go b/internal/promotion/runner/builtin/git_pr_opener.go
index 9e59ae3d..90901d29 100644
--- a/internal/promotion/runner/builtin/git_pr_opener.go
+++ b/internal/promotion/runner/builtin/git_pr_opener.go
@@ -16,15 +16,15 @@ import (
 	"github.com/akuity/kargo/pkg/promotion"
 	"github.com/akuity/kargo/pkg/x/promotion/runner/builtin"
 
-	_ "github.com/akuity/kargo/internal/gitprovider/azure"  // Azure provider registration
+	_ "github.com/akuity/kargo/internal/gitprovider/azure"     // Azure provider registration
 	_ "github.com/akuity/kargo/internal/gitprovider/bitbucket" // Bitbucket provider registration
-	_ "github.com/akuity/kargo/internal/gitprovider/gitea"  // Gitea provider registration
-	_ "github.com/akuity/kargo/internal/gitprovider/github" // GitHub provider registration
-	_ "github.com/akuity/kargo/internal/gitprovider/gitlab" // GitLab provider registration
+	_ "github.com/akuity/kargo/internal/gitprovider/gitea"     // Gitea provider registration
+	_ "github.com/akuity/kargo/internal/gitprovider/github"    // GitHub provider registration
+	_ "github.com/akuity/kargo/internal/gitprovider/gitlab"    // GitLab provider registration
 )
 
 // stateKeyPRNumber is the key used to store the PR number in the shared State.
-// 
+//
 // Deprecated: Use `pr.id` instead.
 //
 // TODO: Remove in v1.7.0
@@ -58,11 +58,11 @@ func (g *gitPROpener) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.GitOpenPRConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into git-open-pr config: %w", err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -88,7 +88,7 @@ func (g *gitPROpener) run(
 		cfg.RepoURL,
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting credentials for %s: %w", cfg.RepoURL, err)
 	}
 	if creds != nil {
@@ -111,7 +111,7 @@ func (g *gitPROpener) run(
 		},
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error cloning %s: %w", cfg.RepoURL, err)
 	}
 	defer repo.Close()
@@ -127,7 +127,7 @@ func (g *gitPROpener) run(
 	}
 	gitProvider, err := gitprovider.New(cfg.RepoURL, gpOpts)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error creating git provider service: %w", err)
 	}
 
@@ -140,13 +140,13 @@ func (g *gitPROpener) run(
 		cfg.TargetBranch,
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error determining if pull request already exists: %w", err)
 	}
 
 	if pr != nil && (pr.Open || pr.Merged) { // Excludes PR that is both closed AND unmerged
 		return promotion.StepResult{
-			Status: kargoapi.PromotionPhaseSucceeded,
+			Status: kargoapi.PromotionStepStatusSucceeded,
 			Output: map[string]any{
 				"pr": map[string]any{
 					"id":  pr.Number,
@@ -166,7 +166,7 @@ func (g *gitPROpener) run(
 	// that may involve creating a new branch and committing to it.
 	commitMsg, err := repo.CommitMessage(sourceBranch)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"error getting commit message from head of branch %s: %w",
 			sourceBranch, err,
 		)
@@ -177,7 +177,7 @@ func (g *gitPROpener) run(
 		cfg.TargetBranch,
 		cfg.CreateTargetBranch,
 	); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"error ensuring existence of remote branch %s: %w",
 			cfg.TargetBranch, err,
 		)
@@ -210,11 +210,11 @@ func (g *gitPROpener) run(
 			Labels:      cfg.Labels,
 		},
 	); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error creating pull request: %w", err)
 	}
 	return promotion.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: map[string]any{
 			"pr": map[string]any{
 				"id":  pr.Number,
diff --git a/internal/promotion/runner/builtin/git_pr_waiter.go b/internal/promotion/runner/builtin/git_pr_waiter.go
index de6146dc..0a865db0 100644
--- a/internal/promotion/runner/builtin/git_pr_waiter.go
+++ b/internal/promotion/runner/builtin/git_pr_waiter.go
@@ -49,11 +49,11 @@ func (g *gitPRWaiter) Run(
 	stepCtx *promoPkg.StepContext,
 ) (promoPkg.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promoPkg.ConfigToStruct[builtin.GitWaitForPRConfig](stepCtx.Config)
 	if err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into git-wait-for-pr config: %w", err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -77,7 +77,7 @@ func (g *gitPRWaiter) run(
 		cfg.RepoURL,
 	)
 	if err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting credentials for %s: %w", cfg.RepoURL, err)
 	}
 	if creds != nil {
@@ -99,25 +99,25 @@ func (g *gitPRWaiter) run(
 	}
 	gitProv, err := gitprovider.New(cfg.RepoURL, gpOpts)
 	if err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error creating git provider service: %w", err)
 	}
 
 	pr, err := gitProv.GetPullRequest(ctx, cfg.PRNumber)
 	if err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting pull request %d: %w", cfg.PRNumber, err)
 	}
 
 	if pr.Open {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseRunning}, nil
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusRunning}, nil
 	}
 	if !pr.Merged {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseFailed},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusFailed},
 			&promotion.TerminalError{Err: fmt.Errorf("pull request %d was closed without being merged", cfg.PRNumber)}
 	}
 	return promoPkg.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: map[string]any{stateKeyCommit: pr.MergeCommitSHA},
 	}, nil
 }
diff --git a/internal/promotion/runner/builtin/git_pusher.go b/internal/promotion/runner/builtin/git_pusher.go
index 3f774d98..6fc0f2d4 100644
--- a/internal/promotion/runner/builtin/git_pusher.go
+++ b/internal/promotion/runner/builtin/git_pusher.go
@@ -53,11 +53,11 @@ func (g *gitPushPusher) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.GitPushConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into git-push config: %w", err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -78,7 +78,7 @@ func (g *gitPushPusher) run(
 	// credentials and, if found, reload the work tree with the credentials.
 	path, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"error joining path %s with work dir %s: %w",
 			cfg.Path, stepCtx.WorkDir, err,
 		)
@@ -86,7 +86,7 @@ func (g *gitPushPusher) run(
 	loadOpts := &git.LoadWorkTreeOptions{}
 	workTree, err := git.LoadWorkTree(path, loadOpts)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error loading working tree from %s: %w", cfg.Path, err)
 	}
 	creds, err := g.credsDB.Get(
@@ -96,7 +96,7 @@ func (g *gitPushPusher) run(
 		workTree.URL(),
 	)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting credentials for %s: %w", workTree.URL(), err)
 	}
 	if creds != nil {
@@ -107,7 +107,7 @@ func (g *gitPushPusher) run(
 		}
 	}
 	if workTree, err = git.LoadWorkTree(path, loadOpts); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error loading working tree from %s: %w", cfg.Path, err)
 	}
 	pushOpts := &git.PushOptions{
@@ -129,7 +129,7 @@ func (g *gitPushPusher) run(
 		// because we will want to return the branch that was pushed to, but we
 		// don't want to mess with the options any further.
 		if pushOpts.TargetBranch, err = workTree.CurrentBranch(); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("error getting current branch: %w", err)
 		}
 	}
@@ -169,20 +169,20 @@ func (g *gitPushPusher) run(
 		if git.IsMergeConflict(err) {
 			// Special case: A merge conflict requires manual resolution and no amount
 			// of retries will fix that.
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseFailed},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusFailed},
 				&promotion.TerminalError{Err: err}
 		}
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error pushing commits to remote: %w", err)
 	}
 
 	commitID, err := workTree.LastCommitID()
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error getting last commit ID: %w", err)
 	}
 	return promotion.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: map[string]any{
 			stateKeyBranch: pushOpts.TargetBranch,
 			stateKeyCommit: commitID,
diff --git a/internal/promotion/runner/builtin/git_tree_clearer.go b/internal/promotion/runner/builtin/git_tree_clearer.go
index bf90e5ea..037a6ce4 100644
--- a/internal/promotion/runner/builtin/git_tree_clearer.go
+++ b/internal/promotion/runner/builtin/git_tree_clearer.go
@@ -38,11 +38,11 @@ func (g *gitTreeClearer) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := g.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.GitClearConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", g.Name(), err)
 	}
 	return g.run(ctx, stepCtx, cfg)
@@ -60,26 +60,26 @@ func (g *gitTreeClearer) run(
 ) (promotion.StepResult, error) {
 	p, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"error joining path %s with work dir %s: %w",
 			cfg.Path, stepCtx.WorkDir, err,
 		)
 	}
 	workTree, err := git.LoadWorkTree(p, nil)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error loading working tree from %s: %w", cfg.Path, err)
 	}
 	// workTree.Clear() won't remove any files that aren't indexed. This is a bit
 	// of a hack to ensure that we don't have any untracked files in the working
 	// tree so that workTree.Clear() will remove everything.
 	if err = workTree.AddAll(); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error adding all files to working tree at %s: %w", cfg.Path, err)
 	}
 	if err = workTree.Clear(); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error clearing working tree at %s: %w", cfg.Path, err)
 	}
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
diff --git a/internal/promotion/runner/builtin/helm_chart_updater.go b/internal/promotion/runner/builtin/helm_chart_updater.go
index b1edfb9b..7aad0d99 100644
--- a/internal/promotion/runner/builtin/helm_chart_updater.go
+++ b/internal/promotion/runner/builtin/helm_chart_updater.go
@@ -58,7 +58,7 @@ func (h *helmChartUpdater) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := h.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -85,7 +85,7 @@ func (h *helmChartUpdater) run(
 ) (promotion.StepResult, error) {
 	absChartPath, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to join path %q: %w", cfg.Path, err)
 	}
 
@@ -93,34 +93,34 @@ func (h *helmChartUpdater) run(
 	chartDependencies, err := readChartDependencies(chartFilePath)
 	if err != nil {
 		return promotion.StepResult{
-			Status: kargoapi.PromotionPhaseErrored,
+			Status: kargoapi.PromotionStepStatusErrored,
 		}, fmt.Errorf("failed to load chart dependencies from %q: %w", chartFilePath, err)
 	}
 
 	changes, err := h.processChartUpdates(cfg, chartDependencies)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	if err = intyaml.SetStringsInFile(chartFilePath, changes); err != nil {
 		return promotion.StepResult{
-			Status: kargoapi.PromotionPhaseErrored,
+			Status: kargoapi.PromotionStepStatusErrored,
 		}, fmt.Errorf("failed to update chart dependencies in %q: %w", chartFilePath, err)
 	}
 
 	helmHome, err := os.MkdirTemp("", "helm-chart-update-")
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to create temporary Helm home directory: %w", err)
 	}
 	defer os.RemoveAll(helmHome)
 
 	newVersions, err := h.updateDependencies(ctx, stepCtx, helmHome, absChartPath, chartDependencies)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
-	result := promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}
+	result := promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}
 	if commitMsg := h.generateCommitMessage(cfg.Path, newVersions); commitMsg != "" {
 		result.Output = map[string]any{
 			"commitMessage": commitMsg,
diff --git a/internal/promotion/runner/builtin/helm_template_runner.go b/internal/promotion/runner/builtin/helm_template_runner.go
index 4b7dc256..9c7ef7ac 100644
--- a/internal/promotion/runner/builtin/helm_template_runner.go
+++ b/internal/promotion/runner/builtin/helm_template_runner.go
@@ -56,7 +56,7 @@ func (h *helmTemplateRunner) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	// Validate the configuration against the JSON Schema
 	if err := validate(
@@ -83,44 +83,44 @@ func (h *helmTemplateRunner) run(
 ) (promotion.StepResult, error) {
 	composedValues, err := h.composeValues(stepCtx.WorkDir, cfg)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to compose values: %w", err)
 	}
 
 	chartRequested, err := h.loadChart(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to load chart from %q: %w", cfg.Path, err)
 	}
 
 	if err = h.checkDependencies(chartRequested); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("missing chart dependencies: %w", err)
 	}
 
 	absOutPath, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.OutPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to join path %q: %w", cfg.OutPath, err)
 	}
 
 	install, err := h.newInstallAction(cfg, stepCtx.Project, absOutPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to initialize Helm action config: %w", err)
 	}
 
 	rls, err := install.RunWithContext(ctx, chartRequested, composedValues)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to render chart: %w", err)
 	}
 
 	if err = h.writeOutput(cfg, rls, absOutPath); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("failed to write rendered chart: %w", err)
 	}
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
 
 // composeValues composes the values from the given values files and set values.
diff --git a/internal/promotion/runner/builtin/http_requester.go b/internal/promotion/runner/builtin/http_requester.go
index 77233f4d..71183011 100644
--- a/internal/promotion/runner/builtin/http_requester.go
+++ b/internal/promotion/runner/builtin/http_requester.go
@@ -51,11 +51,11 @@ func (h *httpRequester) Run(
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
 	if err := h.validate(stepCtx.Config); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 	cfg, err := promotion.ConfigToStruct[builtin.HTTPConfig](stepCtx.Config)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into http config: %w", err)
 	}
 	return h.run(ctx, stepCtx, cfg)
@@ -73,54 +73,54 @@ func (h *httpRequester) run(
 ) (promotion.StepResult, error) {
 	req, err := h.buildRequest(cfg)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error building HTTP request: %w", err)
 	}
 	client, err := h.getClient(cfg)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error creating HTTP client: %w", err)
 	}
 	resp, err := client.Do(req)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error sending HTTP request: %w", err)
 	}
 	defer resp.Body.Close()
 	env, err := h.buildExprEnv(ctx, resp)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error building expression context from HTTP response: %w", err)
 	}
 	success, err := h.wasRequestSuccessful(cfg, resp.StatusCode, env)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error evaluating success criteria: %w", err)
 	}
 	failure, err := h.didRequestFail(cfg, resp.StatusCode, env)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("error evaluating failure criteria: %w", err)
 	}
 	switch {
 	case success && !failure:
 		outputs, err := h.buildOutputs(cfg.Outputs, env)
 		if err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("error extracting outputs from HTTP response: %w", err)
 		}
 		return promotion.StepResult{
-			Status: kargoapi.PromotionPhaseSucceeded,
+			Status: kargoapi.PromotionStepStatusSucceeded,
 			Output: outputs,
 		}, nil
 	case failure:
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseFailed},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusFailed},
 			&promotion.TerminalError{Err: fmt.Errorf(
 				"HTTP (%d) response met failure criteria",
 				resp.StatusCode,
 			)}
 	default:
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseRunning}, nil
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusRunning}, nil
 	}
 }
 
diff --git a/internal/promotion/runner/builtin/json_parser.go b/internal/promotion/runner/builtin/json_parser.go
index 3cdd0981..cae2e4ff 100644
--- a/internal/promotion/runner/builtin/json_parser.go
+++ b/internal/promotion/runner/builtin/json_parser.go
@@ -38,7 +38,7 @@ func (jp *jsonParser) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := jp.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -62,7 +62,7 @@ func (jp *jsonParser) run(
 	stepCtx *promotion.StepContext,
 	cfg builtin.JSONParseConfig,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if cfg.Path == "" {
 		return failure, fmt.Errorf("JSON file path cannot be empty")
@@ -83,7 +83,7 @@ func (jp *jsonParser) run(
 	}
 
 	return promotion.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: extractedValues,
 	}, nil
 }
diff --git a/internal/promotion/runner/builtin/json_updater.go b/internal/promotion/runner/builtin/json_updater.go
index bd060f15..48c0ca76 100644
--- a/internal/promotion/runner/builtin/json_updater.go
+++ b/internal/promotion/runner/builtin/json_updater.go
@@ -39,7 +39,7 @@ func (j *jsonUpdater) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := j.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -63,11 +63,11 @@ func (j *jsonUpdater) run(
 	stepCtx *promotion.StepContext,
 	cfg builtin.JSONUpdateConfig,
 ) (promotion.StepResult, error) {
-	result := promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}
+	result := promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}
 
 	if len(cfg.Updates) > 0 {
 		if err := j.updateFile(stepCtx.WorkDir, cfg.Path, cfg.Updates); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("JSON file update failed: %w", err)
 		}
 
diff --git a/internal/promotion/runner/builtin/kustomize_builder.go b/internal/promotion/runner/builtin/kustomize_builder.go
index a7afc25f..8841d3c8 100644
--- a/internal/promotion/runner/builtin/kustomize_builder.go
+++ b/internal/promotion/runner/builtin/kustomize_builder.go
@@ -52,7 +52,7 @@ func (k *kustomizeBuilder) Run(
 	_ context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	// Validate the configuration against the JSON Schema.
 	if err := validate(k.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), k.Name()); err != nil {
@@ -75,29 +75,29 @@ func (k *kustomizeBuilder) run(
 	// Create a "chrooted" filesystem for the kustomize build.
 	fs, err := securefs.MakeFsOnDiskSecureBuild(stepCtx.WorkDir)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Build the manifests.
 	rm, err := kustomizeBuild(fs, filepath.Join(stepCtx.WorkDir, cfg.Path), cfg.Plugin)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Prepare the output path.
 	outPath, err := securejoin.SecureJoin(stepCtx.WorkDir, cfg.OutPath)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Write the built manifests to the output path.
 	if err := k.writeResult(rm, outPath); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, fmt.Errorf(
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, fmt.Errorf(
 			"failed to write built manifests to %q: %w", cfg.OutPath,
 			sanitizePathError(err, stepCtx.WorkDir),
 		)
 	}
-	return promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, nil
+	return promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, nil
 }
 
 func (k *kustomizeBuilder) writeResult(rm resmap.ResMap, outPath string) error {
diff --git a/internal/promotion/runner/builtin/kustomize_image_setter.go b/internal/promotion/runner/builtin/kustomize_image_setter.go
index 816a9d36..c581833a 100644
--- a/internal/promotion/runner/builtin/kustomize_image_setter.go
+++ b/internal/promotion/runner/builtin/kustomize_image_setter.go
@@ -55,7 +55,7 @@ func (k *kustomizeImageSetter) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := k.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -83,7 +83,7 @@ func (k *kustomizeImageSetter) run(
 	// Find the Kustomization file.
 	kusPath, err := findKustomization(stepCtx.WorkDir, cfg.Path)
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not discover kustomization file: %w", err)
 	}
 
@@ -97,15 +97,15 @@ func (k *kustomizeImageSetter) run(
 		targetImages, err = k.buildTargetImagesAutomatically(ctx, stepCtx)
 	}
 	if err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Update the Kustomization file with the new images.
 	if err = updateKustomizationFile(kusPath, targetImages); err != nil {
-		return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
-	result := promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}
+	result := promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}
 	if commitMsg := k.generateCommitMessage(cfg.Path, targetImages); commitMsg != "" {
 		result.Output = map[string]any{
 			"commitMessage": commitMsg,
diff --git a/internal/promotion/runner/builtin/output_composer.go b/internal/promotion/runner/builtin/output_composer.go
index 7755dfe3..2c958159 100644
--- a/internal/promotion/runner/builtin/output_composer.go
+++ b/internal/promotion/runner/builtin/output_composer.go
@@ -58,13 +58,13 @@ func (c *outputComposer) Run(
 ) (promoPkg.StepResult, error) {
 	// Validate the configuration against the JSON Schema.
 	if err := validate(c.schemaLoader, gojsonschema.NewGoLoader(stepCtx.Config), c.Name()); err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored}, err
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored}, err
 	}
 
 	// Convert the configuration into a typed object.
 	cfg, err := promoPkg.ConfigToStruct[builtin.ComposeOutput](stepCtx.Config)
 	if err != nil {
-		return promoPkg.StepResult{Status: kargoapi.PromotionPhaseErrored},
+		return promoPkg.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 			fmt.Errorf("could not convert config into %s config: %w", c.Name(), err)
 	}
 
@@ -75,7 +75,7 @@ func (c *outputComposer) run(
 	cfg builtin.ComposeOutput,
 ) (promoPkg.StepResult, error) {
 	return promoPkg.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: maps.Clone(cfg),
 	}, nil
 }
diff --git a/internal/promotion/runner/builtin/schemas/git-clone-config.json b/internal/promotion/runner/builtin/schemas/git-clone-config.json
index 8927cada..1a832f90 100644
--- a/internal/promotion/runner/builtin/schemas/git-clone-config.json
+++ b/internal/promotion/runner/builtin/schemas/git-clone-config.json
@@ -14,6 +14,28 @@
       "description": "The URL of a remote Git repository to clone. Required.",
       "minLength": 1
     },
+    "author": {
+      "type": "object",
+      "description": "Default authorship information for any commits made to the cloned repository. If provided, this overrides any system-level defaults. Note: Configuration of the `git-commit` step can override this information.",
+      "additionalProperties": false,
+      "properties": {
+        "email": {
+          "type": "string",
+          "description": "The email of the author.",
+          "format": "email"
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the author.",
+          "minLength": 1
+        },
+        "signingKey": {
+          "type": "string",
+          "description": "The GPG signing key for the author."
+        }
+      },
+      "required": ["name", "email"]
+    },
     "checkout": {
       "type": "array",
       "description": "The commits, branches, or tags to check out from the repository and the paths where they should be checked out. At least one must be specified.",
diff --git a/internal/promotion/runner/builtin/schemas/git-commit-config.json b/internal/promotion/runner/builtin/schemas/git-commit-config.json
index 18026ebf..de4214fa 100644
--- a/internal/promotion/runner/builtin/schemas/git-commit-config.json
+++ b/internal/promotion/runner/builtin/schemas/git-commit-config.json
@@ -7,26 +7,25 @@
   "properties": {
     "author": {
       "type": "object",
-      "description": "The author of the commit.",
+      "description": "Optional authorship information for the commit. If provided, this takes precedence over both system-level defaults and any optional, default authorship information configured in the `git-clone` step.",
       "additionalProperties": false,
       "properties": {
         "email": {
           "type": "string",
           "description": "The email of the author.",
-          "oneOf": [
-            {
-              "format": "email"
-            },
-            {
-              "const": ""
-            }
-          ]
+          "format": "email"
         },
         "name": {
           "type": "string",
-          "description": "The name of the author."
+          "description": "The name of the author.",
+          "minLength": 1
+        },
+        "signingKey": {
+          "type": "string",
+          "description": "The GPG signing key for the author."
         }
-      }
+      },
+      "required": ["name", "email"]
     },
     "message": {
       "type": "string",
diff --git a/internal/promotion/runner/builtin/yaml_parser.go b/internal/promotion/runner/builtin/yaml_parser.go
index 1dd04157..65f3d207 100644
--- a/internal/promotion/runner/builtin/yaml_parser.go
+++ b/internal/promotion/runner/builtin/yaml_parser.go
@@ -38,7 +38,7 @@ func (yp *yamlParser) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := yp.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -62,7 +62,7 @@ func (yp *yamlParser) run(
 	stepCtx *promotion.StepContext,
 	cfg builtin.YAMLParseConfig,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if cfg.Path == "" {
 		return failure, fmt.Errorf("YAML file path cannot be empty")
@@ -83,7 +83,7 @@ func (yp *yamlParser) run(
 	}
 
 	return promotion.StepResult{
-		Status: kargoapi.PromotionPhaseSucceeded,
+		Status: kargoapi.PromotionStepStatusSucceeded,
 		Output: extractedValues,
 	}, nil
 }
diff --git a/internal/promotion/runner/builtin/yaml_updater.go b/internal/promotion/runner/builtin/yaml_updater.go
index 44aa0884..9950a095 100644
--- a/internal/promotion/runner/builtin/yaml_updater.go
+++ b/internal/promotion/runner/builtin/yaml_updater.go
@@ -38,7 +38,7 @@ func (y *yamlUpdater) Run(
 	ctx context.Context,
 	stepCtx *promotion.StepContext,
 ) (promotion.StepResult, error) {
-	failure := promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}
+	failure := promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}
 
 	if err := y.validate(stepCtx.Config); err != nil {
 		return failure, err
@@ -71,10 +71,10 @@ func (y *yamlUpdater) run(
 		}
 	}
 
-	result := promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}
+	result := promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}
 	if len(updates) > 0 {
 		if err := y.updateFile(stepCtx.WorkDir, cfg.Path, updates); err != nil {
-			return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+			return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 				fmt.Errorf("values file update failed: %w", err)
 		}
 
diff --git a/internal/promotion/simple_engine.go b/internal/promotion/simple_engine.go
index 9b005d76..217474e0 100644
--- a/internal/promotion/simple_engine.go
+++ b/internal/promotion/simple_engine.go
@@ -151,12 +151,7 @@ func (e *simpleEngine) executeSteps(
 				HealthChecks:          healthChecks,
 			}, fmt.Errorf("error checking if step %d should be skipped: %w", i, err)
 		} else if skip {
-			// TODO(hidde): We should probably set the status to skipped here,
-			// but this would require step specific phases (opposed to the
-			// promotion wide phases we have now). We should revisit this when
-			// we dive into the other engine related changes (e.g. improved
-			// failure handling).
-			stepExecMeta.Status = kargoapi.PromotionPhaseSucceeded
+			stepExecMeta.Status = kargoapi.PromotionStepStatusSkipped
 			continue
 		}
 
@@ -185,8 +180,9 @@ func (e *simpleEngine) executeSteps(
 		}
 
 		switch result.Status {
-		case kargoapi.PromotionPhaseErrored, kargoapi.PromotionPhaseFailed,
-			kargoapi.PromotionPhaseRunning, kargoapi.PromotionPhaseSucceeded:
+		case kargoapi.PromotionStepStatusErrored, kargoapi.PromotionStepStatusFailed,
+			kargoapi.PromotionStepStatusRunning, kargoapi.PromotionStepStatusSucceeded,
+			kargoapi.PromotionStepStatusSkipped: // Step runners can self-determine they should be skipped
 		default:
 			// Deal with statuses that no step should have returned.
 			stepExecMeta.FinishedAt = ptr.To(metav1.Now())
@@ -201,16 +197,16 @@ func (e *simpleEngine) executeSteps(
 
 		// Reconcile status and err...
 		if err != nil {
-			if stepExecMeta.Status != kargoapi.PromotionPhaseFailed {
+			if stepExecMeta.Status != kargoapi.PromotionStepStatusFailed {
 				// All states other than Errored and Failed should be mutually exclusive
 				// with a hard error. If we got to here, a step has violated this
 				// assumption. We will prioritize the error over the status and change
 				// the status to Errored.
-				stepExecMeta.Status = kargoapi.PromotionPhaseErrored
+				stepExecMeta.Status = kargoapi.PromotionStepStatusErrored
 			}
 			// Let the hard error take precedence over the message.
 			stepExecMeta.Message = err.Error()
-		} else if result.Status == kargoapi.PromotionPhaseErrored {
+		} else if result.Status == kargoapi.PromotionStepStatusErrored {
 			// A nil err should be mutually exclusive with an Errored status. If we
 			// got to here, a step has violated this assumption. We will prioritize
 			// the Errored status over the nil error and create an error.
@@ -225,7 +221,7 @@ func (e *simpleEngine) executeSteps(
 		// err.
 
 		switch {
-		case stepExecMeta.Status == kargoapi.PromotionPhaseSucceeded:
+		case stepExecMeta.Status == kargoapi.PromotionStepStatusSucceeded:
 			// Best case scenario: The step succeeded.
 			stepExecMeta.FinishedAt = ptr.To(metav1.Now())
 			if healthCheck := result.HealthCheck; healthCheck != nil {
@@ -236,7 +232,7 @@ func (e *simpleEngine) executeSteps(
 			// This is an unrecoverable error.
 			stepExecMeta.FinishedAt = ptr.To(metav1.Now())
 			return Result{
-				Status:                stepExecMeta.Status,
+				Status:                kargoapi.PromotionPhaseErrored,
 				CurrentStep:           i,
 				StepExecutionMetadata: stepExecMetas,
 				State:                 state,
@@ -328,13 +324,9 @@ func (e *simpleEngine) executeStep(
 ) (promotion.StepResult, error) {
 	stepCtx, err := e.prepareStepContext(ctx, promoCtx, step, workDir, state)
 	if err != nil {
-		// TODO(krancour): We're not yet distinguishing between retryable and
-		// non-retryable errors. When we start to do this, failure to prepare the
-		// step context (likely due to invalid configuration) should be considered
-		// non-retryable.
 		return promotion.StepResult{
-			Status: kargoapi.PromotionPhaseErrored,
-		}, err
+			Status: kargoapi.PromotionStepStatusErrored,
+		}, &promotion.TerminalError{Err: err}
 	}
 
 	result, err := runner.Run(ctx, stepCtx)
diff --git a/pkg/promotion/promotion.go b/pkg/promotion/promotion.go
index b2f3fefc..8bea327d 100644
--- a/pkg/promotion/promotion.go
+++ b/pkg/promotion/promotion.go
@@ -81,7 +81,7 @@ type StepContext struct {
 // process executed by a StepRunner.
 type StepResult struct {
 	// Status is the high-level outcome of a Step executed by a StepRunner.
-	Status kargoapi.PromotionPhase
+	Status kargoapi.PromotionStepStatus
 	// Message is an optional message that provides additional context about the
 	// outcome of a Step executed by a StepRunner.
 	Message string
diff --git a/pkg/x/promotion/runner/builtin/zz_config_types.go b/pkg/x/promotion/runner/builtin/zz_config_types.go
index fca3afa9..3df28bd6 100644
--- a/pkg/x/promotion/runner/builtin/zz_config_types.go
+++ b/pkg/x/promotion/runner/builtin/zz_config_types.go
@@ -105,6 +105,10 @@ type GitClearConfig struct {
 }
 
 type GitCloneConfig struct {
+	// Default authorship information for any commits made to the cloned repository. If
+	// provided, this overrides any system-level defaults. Note: Configuration of the
+	// `git-commit` step can override this information.
+	Author *GitCloneConfigAuthor `json:"author,omitempty"`
 	// The commits, branches, or tags to check out from the repository and the paths where they
 	// should be checked out. At least one must be specified.
 	Checkout []Checkout `json:"checkout"`
@@ -114,6 +118,18 @@ type GitCloneConfig struct {
 	RepoURL string `json:"repoURL"`
 }
 
+// Default authorship information for any commits made to the cloned repository. If
+// provided, this overrides any system-level defaults. Note: Configuration of the
+// `git-commit` step can override this information.
+type GitCloneConfigAuthor struct {
+	// The email of the author.
+	Email string `json:"email"`
+	// The name of the author.
+	Name string `json:"name"`
+	// The GPG signing key for the author.
+	SigningKey string `json:"signingKey,omitempty"`
+}
+
 type Checkout struct {
 	// The branch to checkout. Mutually exclusive with 'commit' and 'tag'. If none of these are
 	// specified, the default branch is checked out.
@@ -132,20 +148,26 @@ type Checkout struct {
 }
 
 type GitCommitConfig struct {
-	// The author of the commit.
-	Author *Author `json:"author,omitempty"`
+	// Optional authorship information for the commit. If provided, this takes precedence over
+	// both system-level defaults and any optional, default authorship information configured in
+	// the `git-clone` step.
+	Author *GitCommitConfigAuthor `json:"author,omitempty"`
 	// The commit message.
 	Message string `json:"message"`
 	// The path to a working directory of a local repository.
 	Path string `json:"path"`
 }
 
-// The author of the commit.
-type Author struct {
+// Optional authorship information for the commit. If provided, this takes precedence over
+// both system-level defaults and any optional, default authorship information configured in
+// the `git-clone` step.
+type GitCommitConfigAuthor struct {
 	// The email of the author.
-	Email string `json:"email,omitempty"`
+	Email string `json:"email"`
 	// The name of the author.
-	Name string `json:"name,omitempty"`
+	Name string `json:"name"`
+	// The GPG signing key for the author.
+	SigningKey string `json:"signingKey,omitempty"`
 }
 
 type GitOpenPRConfig struct {
diff --git a/ui/src/features/common/promotion-directive-step-status/promotion-step-status.ts b/ui/src/features/common/promotion-directive-step-status/promotion-step-status.ts
new file mode 100644
index 00000000..c3c1bf44
--- /dev/null
+++ b/ui/src/features/common/promotion-directive-step-status/promotion-step-status.ts
@@ -0,0 +1,8 @@
+export enum PromotionStepStatus {
+  RUNNING = 'Running',
+  SUCCEEDED = 'Succeeded',
+  FAILED = 'Failed',
+  ERRORED = 'Errored',
+  ABORTED = 'Aborted',
+  SKIPPED = 'Skipped'
+}
diff --git a/ui/src/features/common/promotion-directive-step-status/utils.test.ts b/ui/src/features/common/promotion-directive-step-status/utils.test.ts
deleted file mode 100644
index 0823ca10..00000000
--- a/ui/src/features/common/promotion-directive-step-status/utils.test.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-import { expect, test } from 'vitest';
-
-import { PromotionStatus } from '@ui/gen/api/v1alpha1/generated_pb';
-import { PlainMessage } from '@ui/utils/connectrpc-utils';
-
-import { PromotionStatusPhase } from '../promotion-status/utils';
-
-import { getPromotionDirectiveStepStatus, PromotionDirectiveStepStatus } from './utils';
-
-test('getPromotionDirectiveStepStatus', () => {
-  const promotionStatus = (phase: PromotionStatusPhase, currentStep: number) =>
-    ({
-      phase,
-      currentStep: BigInt(currentStep)
-    }) satisfies Partial<PlainMessage<PromotionStatus>> as PromotionStatus;
-
-  const suite: Record<
-    PromotionDirectiveStepStatus,
-    Array<{
-      step: number;
-      promotionStatus: {
-        currentStep: number;
-        phase: PromotionStatusPhase;
-      };
-    }>
-  > = {
-    [PromotionDirectiveStepStatus.RUNNING]: [
-      {
-        step: 0,
-        promotionStatus: {
-          phase: PromotionStatusPhase.RUNNING,
-          currentStep: 0
-        }
-      },
-      {
-        step: 100000,
-        promotionStatus: {
-          phase: PromotionStatusPhase.RUNNING,
-          currentStep: 100000
-        }
-      }
-    ],
-    [PromotionDirectiveStepStatus.FAILED]: [
-      {
-        step: 5,
-        promotionStatus: {
-          phase: PromotionStatusPhase.FAILED,
-          currentStep: 5
-        }
-      },
-      {
-        step: 111,
-        promotionStatus: {
-          phase: PromotionStatusPhase.FAILED,
-          currentStep: 111
-        }
-      },
-      {
-        step: 1353,
-        promotionStatus: {
-          phase: PromotionStatusPhase.ERRORED,
-          currentStep: 1353
-        }
-      },
-      {
-        step: 1241243,
-        promotionStatus: {
-          phase: PromotionStatusPhase.FAILED,
-          currentStep: 1241243
-        }
-      }
-    ],
-    [PromotionDirectiveStepStatus.SUCCESS]: [
-      {
-        step: 1,
-        promotionStatus: {
-          phase: PromotionStatusPhase.SUCCEEDED,
-          currentStep: 100
-        }
-      },
-      {
-        step: 1,
-        promotionStatus: {
-          phase: PromotionStatusPhase.SUCCEEDED,
-          currentStep: 1
-        }
-      },
-      {
-        step: 1,
-        promotionStatus: {
-          phase: PromotionStatusPhase.RUNNING,
-          currentStep: 100
-        }
-      },
-      {
-        step: 123123,
-        promotionStatus: {
-          phase: PromotionStatusPhase.RUNNING,
-          currentStep: 123124
-        }
-      },
-      {
-        step: 12,
-        promotionStatus: {
-          phase: PromotionStatusPhase.FAILED,
-          currentStep: 1231
-        }
-      },
-      {
-        step: 12,
-        promotionStatus: {
-          phase: PromotionStatusPhase.ERRORED,
-          currentStep: 15
-        }
-      },
-      {
-        step: 11,
-        promotionStatus: {
-          phase: PromotionStatusPhase.ABORTED,
-          currentStep: 15
-        }
-      }
-    ],
-    [PromotionDirectiveStepStatus.WONT_RUN]: [
-      {
-        step: 15,
-        promotionStatus: {
-          currentStep: 10,
-          phase: PromotionStatusPhase.ERRORED
-        }
-      },
-      {
-        step: 15,
-        promotionStatus: {
-          currentStep: 10,
-          phase: PromotionStatusPhase.RUNNING
-        }
-      },
-      {
-        step: 1111,
-        promotionStatus: {
-          currentStep: 111,
-          phase: PromotionStatusPhase.FAILED
-        }
-      },
-      {
-        step: 20,
-        promotionStatus: {
-          currentStep: 2,
-          phase: PromotionStatusPhase.PENDING
-        }
-      },
-      {
-        step: 0,
-        promotionStatus: {
-          currentStep: 0,
-          phase: PromotionStatusPhase.PENDING
-        }
-      },
-      {
-        step: 0,
-        promotionStatus: {
-          currentStep: 0,
-          phase: PromotionStatusPhase.ABORTED
-        }
-      },
-      {
-        step: 100,
-        promotionStatus: {
-          currentStep: 90,
-          phase: PromotionStatusPhase.ABORTED
-        }
-      }
-    ]
-  };
-
-  for (const [expectedStatus, testCases] of Object.entries(suite)) {
-    for (const testCase of testCases) {
-      expect(
-        PromotionDirectiveStepStatus[
-          getPromotionDirectiveStepStatus(
-            testCase.step,
-            promotionStatus(testCase.promotionStatus.phase, testCase.promotionStatus.currentStep)
-          )
-        ],
-        // @ts-expect-error really not sure why but its working...
-        `when promotion phase is in ${testCase.promotionStatus.phase} state and promotion current step is ${testCase.promotionStatus.currentStep}, step ${testCase.step} must be in ${PromotionDirectiveStepStatus[expectedStatus]}`
-      ).toBe(PromotionDirectiveStepStatus[+expectedStatus]);
-    }
-  }
-});
diff --git a/ui/src/features/common/promotion-directive-step-status/utils.ts b/ui/src/features/common/promotion-directive-step-status/utils.ts
index 29d4ea92..0ac71d8b 100644
--- a/ui/src/features/common/promotion-directive-step-status/utils.ts
+++ b/ui/src/features/common/promotion-directive-step-status/utils.ts
@@ -1,11 +1,13 @@
-import { PromotionStatusPhase } from '@ui/features/common/promotion-status/utils';
 import { PromotionStatus } from '@ui/gen/api/v1alpha1/generated_pb';
 
+import { PromotionStepStatus } from './promotion-step-status';
+
 // UI concludes from Promotion's status data
 export enum PromotionDirectiveStepStatus {
   RUNNING,
   FAILED,
   SUCCESS,
+  SKIPPED,
   WONT_RUN // because previous step failed
 }
 
@@ -13,31 +15,21 @@ export const getPromotionDirectiveStepStatus = (
   stepNumber: number,
   promotionStatus?: PromotionStatus
 ) => {
-  let currentStep = 0;
-
-  // there will always be some value of currentStep
-  // this is just for type saftey
-  if (promotionStatus?.currentStep) {
-    currentStep = Number(promotionStatus.currentStep);
-  }
-
-  if (stepNumber < currentStep) {
-    // assumes that controller successfully ran this step
-    return PromotionDirectiveStepStatus.SUCCESS;
-  }
+  const promotionStepStatus = promotionStatus?.stepExecutionMetadata?.[stepNumber]
+    ?.status as PromotionStepStatus;
 
-  if (stepNumber === currentStep) {
-    switch (promotionStatus?.phase) {
-      case PromotionStatusPhase.RUNNING:
-        return PromotionDirectiveStepStatus.RUNNING;
-      case PromotionStatusPhase.ERRORED:
-      case PromotionStatusPhase.FAILED:
-        return PromotionDirectiveStepStatus.FAILED;
-      case PromotionStatusPhase.SUCCEEDED:
-        return PromotionDirectiveStepStatus.SUCCESS;
-    }
+  switch (promotionStepStatus) {
+    case PromotionStepStatus.RUNNING:
+      return PromotionDirectiveStepStatus.RUNNING;
+    case PromotionStepStatus.SKIPPED:
+      return PromotionDirectiveStepStatus.SKIPPED;
+    case PromotionStepStatus.SUCCEEDED:
+      return PromotionDirectiveStepStatus.SUCCESS;
+    case PromotionStepStatus.ABORTED:
+    case PromotionStepStatus.ERRORED:
+    case PromotionStepStatus.FAILED:
+      return PromotionDirectiveStepStatus.FAILED;
   }
 
-  // if step number is > current step, no matter which state promotion is in, the given step number "has not" run yet or "will not" depends on the promotion phase but that doesn't matter....YET
   return PromotionDirectiveStepStatus.WONT_RUN;
 };
diff --git a/ui/src/features/stage/promotion-details-modal.tsx b/ui/src/features/stage/promotion-details-modal.tsx
index bd8dbc34..266230bf 100644
--- a/ui/src/features/stage/promotion-details-modal.tsx
+++ b/ui/src/features/stage/promotion-details-modal.tsx
@@ -1,6 +1,7 @@
 import { toJson } from '@bufbuild/protobuf';
 import { useMutation } from '@connectrpc/connect-query';
 import {
+  faBan,
   faCheck,
   faCircleNotch,
   faCog,
@@ -76,6 +77,7 @@ const Step = ({
   const progressing = result === PromotionDirectiveStepStatus.RUNNING;
   const success = result === PromotionDirectiveStepStatus.SUCCESS;
   const failed = result === PromotionDirectiveStepStatus.FAILED;
+  const skipped = result === PromotionDirectiveStepStatus.SKIPPED;
 
   const opts: SegmentedOptions<string> = [];
 
@@ -133,6 +135,7 @@ const Step = ({
           {progressing && <FontAwesomeIcon spin icon={faCircleNotch} />}
           {success && <FontAwesomeIcon icon={faCheck} className='text-green-500' />}
           {failed && <FontAwesomeIcon icon={faTimes} className='text-red-500' />}
+          {skipped && <FontAwesomeIcon icon={faBan} />}
         </Flex>
         <Flex className={'w-full'} align='center'>
           <span className='font-semibold text-base '>{meta.spec.identifier}</span>
diff --git a/ui/src/gen/directives/git-clone-config.json b/ui/src/gen/directives/git-clone-config.json
index 6da2e6a3..7e26261a 100644
--- a/ui/src/gen/directives/git-clone-config.json
+++ b/ui/src/gen/directives/git-clone-config.json
@@ -13,6 +13,27 @@
    "description": "The URL of a remote Git repository to clone. Required.",
    "minLength": 1
   },
+  "author": {
+   "type": "object",
+   "description": "Default authorship information for any commits made to the cloned repository. If provided, this overrides any system-level defaults. Note: Configuration of the `git-commit` step can override this information.",
+   "additionalProperties": false,
+   "properties": {
+    "email": {
+     "type": "string",
+     "description": "The email of the author.",
+     "format": "email"
+    },
+    "name": {
+     "type": "string",
+     "description": "The name of the author.",
+     "minLength": 1
+    },
+    "signingKey": {
+     "type": "string",
+     "description": "The GPG signing key for the author."
+    }
+   }
+  },
   "checkout": {
    "type": "array",
    "description": "The commits, branches, or tags to check out from the repository and the paths where they should be checked out. At least one must be specified.",
diff --git a/ui/src/gen/directives/git-commit-config.json b/ui/src/gen/directives/git-commit-config.json
index 9fc27a62..5c38beb6 100644
--- a/ui/src/gen/directives/git-commit-config.json
+++ b/ui/src/gen/directives/git-commit-config.json
@@ -6,16 +6,22 @@
  "properties": {
   "author": {
    "type": "object",
-   "description": "The author of the commit.",
+   "description": "Optional authorship information for the commit. If provided, this takes precedence over both system-level defaults and any optional, default authorship information configured in the `git-clone` step.",
    "additionalProperties": false,
    "properties": {
     "email": {
      "type": "string",
-     "description": "The email of the author."
+     "description": "The email of the author.",
+     "format": "email"
     },
     "name": {
      "type": "string",
-     "description": "The name of the author."
+     "description": "The name of the author.",
+     "minLength": 1
+    },
+    "signingKey": {
+     "type": "string",
+     "description": "The GPG signing key for the author."
     }
    }
   },



Test patch:
diff --git a/internal/controller/promotions/promotions_test.go b/internal/controller/promotions/promotions_test.go
index b2fc49e7..fe2bfdd8 100644
--- a/internal/controller/promotions/promotions_test.go
+++ b/internal/controller/promotions/promotions_test.go
@@ -361,7 +361,41 @@ func Test_reconciler_terminatePromotion(t *testing.T) {
 				require.NoError(t, err)
 				require.Equal(t, kargoapi.PromotionPhaseAborted, promo.Status.Phase)
 				require.Contains(t, promo.Status.Message, "terminated")
-				require.NotNil(t, now, promo.Status.FinishedAt)
+				require.NotNil(t, promo.Status.FinishedAt)
+
+				require.Len(t, recorder.Events, 1)
+				event := <-recorder.Events
+				require.Equal(t, kargoapi.EventReasonPromotionAborted, event.Reason)
+			},
+		},
+		{
+			name: "terminates running promotion",
+			promo: &kargoapi.Promotion{
+				ObjectMeta: metav1.ObjectMeta{
+					CreationTimestamp: now,
+					Name:              "fake-promo",
+					Namespace:         "fake-namespace",
+				},
+				Spec: kargoapi.PromotionSpec{
+					Stage: "fake-stage",
+				},
+				Status: kargoapi.PromotionStatus{
+					Phase:       kargoapi.PromotionPhaseRunning,
+					CurrentStep: 0,
+					StepExecutionMetadata: []kargoapi.StepExecutionMetadata{{
+						StartedAt: &now,
+						Status:    kargoapi.PromotionStepStatusRunning,
+					}},
+				},
+			},
+			assertions: func(t *testing.T, recorder *fakeevent.EventRecorder, promo *kargoapi.Promotion, err error) {
+				require.NoError(t, err)
+				require.Equal(t, kargoapi.PromotionPhaseAborted, promo.Status.Phase)
+				require.Contains(t, promo.Status.Message, "terminated")
+				require.NotNil(t, promo.Status.FinishedAt)
+
+				require.Equal(t, kargoapi.PromotionStepStatusAborted, promo.Status.StepExecutionMetadata[0].Status)
+				require.NotNil(t, promo.Status.StepExecutionMetadata[0].FinishedAt)
 
 				require.Len(t, recorder.Events, 1)
 				event := <-recorder.Events
@@ -384,7 +418,7 @@ func Test_reconciler_terminatePromotion(t *testing.T) {
 				require.NoError(t, err)
 				require.Equal(t, kargoapi.PromotionPhaseAborted, promo.Status.Phase)
 				require.Contains(t, promo.Status.Message, "terminated")
-				require.NotNil(t, now, promo.Status.FinishedAt)
+				require.NotNil(t, promo.Status.FinishedAt)
 
 				require.Len(t, recorder.Events, 1)
 				event := <-recorder.Events
diff --git a/internal/promotion/runner/builtin/argocd_updater_test.go b/internal/promotion/runner/builtin/argocd_updater_test.go
index 07895dae..c9d51d66 100644
--- a/internal/promotion/runner/builtin/argocd_updater_test.go
+++ b/internal/promotion/runner/builtin/argocd_updater_test.go
@@ -346,7 +346,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 			runner:  &argocdUpdater{},
 			stepCfg: builtin.ArgoCDUpdateConfig{},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(
 					t, err, "Argo CD integration is disabled on this controller",
 				)
@@ -368,7 +368,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(t, err, "error getting Argo CD Application")
 				require.ErrorContains(t, err, "something went wrong")
 			},
@@ -396,7 +396,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(t, err, "something went wrong")
 			},
 		},
@@ -438,7 +438,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseRunning, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusRunning, res.Status)
 				require.NoError(t, err)
 			},
 		},
@@ -465,7 +465,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseRunning, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusRunning, res.Status)
 				require.NoError(t, err)
 			},
 		},
@@ -492,7 +492,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseRunning, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusRunning, res.Status)
 				require.NoError(t, err)
 			},
 		},
@@ -526,7 +526,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(t, err, "error building desired sources for Argo CD Application")
 				require.ErrorContains(t, err, "something went wrong")
 			},
@@ -569,7 +569,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(t, err, "error syncing Argo CD Application")
 				require.ErrorContains(t, err, "something went wrong")
 			},
@@ -623,7 +623,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}, {}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.NoError(t, err)
 			},
 		},
@@ -650,7 +650,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 				require.ErrorContains(t, err, "could not determine promotion step status")
 			},
 		},
@@ -677,7 +677,7 @@ func Test_argoCDUpdater_run(t *testing.T) {
 				Apps: []builtin.ArgoCDAppUpdate{{}},
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
-				require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 				require.NoError(t, err)
 			},
 		},
diff --git a/internal/promotion/runner/builtin/file_copier_test.go b/internal/promotion/runner/builtin/file_copier_test.go
index 52e62fcf..3464e8dc 100644
--- a/internal/promotion/runner/builtin/file_copier_test.go
+++ b/internal/promotion/runner/builtin/file_copier_test.go
@@ -40,7 +40,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outPath := filepath.Join(workDir, "output.txt")
 				b, err := os.ReadFile(outPath)
@@ -72,7 +72,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outDir := filepath.Join(workDir, "output")
 
@@ -110,7 +110,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outDir := filepath.Join(workDir, "output")
 
@@ -148,7 +148,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outDir := filepath.Join(workDir, "output")
 
@@ -184,7 +184,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outDir := filepath.Join(workDir, "output")
 
@@ -221,7 +221,7 @@ func Test_fileCopier_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outDir := filepath.Join(workDir, "output")
 
@@ -245,7 +245,7 @@ func Test_fileCopier_run(t *testing.T) {
 				InPath: "input.txt",
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				require.ErrorContains(t, err, "failed to copy")
 			},
 		},
diff --git a/internal/promotion/runner/builtin/file_deleter_test.go b/internal/promotion/runner/builtin/file_deleter_test.go
index 3c04c49d..2a65744e 100644
--- a/internal/promotion/runner/builtin/file_deleter_test.go
+++ b/internal/promotion/runner/builtin/file_deleter_test.go
@@ -36,7 +36,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				_, statError := os.Stat("input.txt")
 				assert.True(t, os.IsNotExist(statError))
@@ -55,7 +55,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				_, statErr := os.Stat(filepath.Join(workDir, "dirToDelete"))
 				assert.True(t, os.IsNotExist(statErr))
@@ -72,7 +72,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 			},
 		},
 		{
@@ -86,7 +86,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 			},
 		},
 		{
@@ -110,7 +110,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				_, statErr := os.Stat(filepath.Join(workDir, "input", "input.txt"))
 				assert.NoError(t, statErr)
@@ -141,7 +141,7 @@ func Test_fileDeleter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				require.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				_, statErr := os.Stat(filepath.Join(workDir, "foo", "file.txt"))
 				assert.Error(t, statErr)
diff --git a/internal/promotion/runner/builtin/git_cloner_test.go b/internal/promotion/runner/builtin/git_cloner_test.go
index eb0c7c3e..7f6bdc0f 100644
--- a/internal/promotion/runner/builtin/git_cloner_test.go
+++ b/internal/promotion/runner/builtin/git_cloner_test.go
@@ -115,6 +115,112 @@ func Test_gitCloner_validate(t *testing.T) {
 				"checkout.0: Must validate one and only one schema",
 			},
 		},
+		{
+			name: "author name is missing",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/0",
+					},
+				},
+				"author": promotion.Config{
+					"email": "tony@starkindustries.com",
+					// Missing "name"
+				},
+			},
+			expectedProblems: []string{
+				"invalid git-clone config: author: name is required",
+			},
+		},
+		{
+			name: "author name is empty",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/0",
+					},
+				},
+				"author": promotion.Config{
+					"name":  "",
+					"email": "tony@starkindustries.com",
+				},
+			},
+			expectedProblems: []string{
+				"invalid git-clone config: author.name: String length must be greater than or equal to 1",
+			},
+		},
+		{
+			name: "author email is missing",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/1",
+					},
+				},
+				"author": promotion.Config{
+					"name": "Tony Stark",
+					// Missing "email"
+				},
+			},
+			expectedProblems: []string{
+				"invalid git-clone config: author: email is required",
+			},
+		},
+		{
+			name: "author email is empty",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/1",
+					},
+				},
+				"author": promotion.Config{
+					"name":  "Tony Stark",
+					"email": "",
+				},
+			},
+			expectedProblems: []string{
+				"invalid git-clone config: author.email: Does not match format 'email'",
+			},
+		},
+		{
+			name: "signingKey is missing",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/2",
+					},
+				},
+				"author": promotion.Config{
+					"name":  "Tony Stark",
+					"email": "tony@starkindustries.com",
+					// signingKey is absent
+				},
+			},
+			// No expected problems because signingKey is optional
+		},
+		{
+			name: "signingKey is empty string",
+			config: promotion.Config{
+				"repoURL": "https://github.com/example/repo.git",
+				"checkout": []promotion.Config{
+					{
+						"path": "/fake/path/1",
+					},
+				},
+				"author": promotion.Config{
+					"name":       "Tony Stark",
+					"email":      "tony@starkindustries.com",
+					"signingKey": "", // Empty string for signing key
+				},
+			},
+			// No expected problems because signingKey is optional and empty is valid
+		},
 		{
 			name: "valid kitchen sink",
 			config: promotion.Config{
@@ -252,7 +358,7 @@ func Test_gitCloner_run(t *testing.T) {
 		},
 	)
 	require.NoError(t, err)
-	require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+	require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 	require.DirExists(t, filepath.Join(stepCtx.WorkDir, "src"))
 	// The checked out master branch should have the content we know is in the
 	// test remote's master branch.
diff --git a/internal/promotion/runner/builtin/git_commiter_test.go b/internal/promotion/runner/builtin/git_commiter_test.go
index 4cc3e44a..620a16c9 100644
--- a/internal/promotion/runner/builtin/git_commiter_test.go
+++ b/internal/promotion/runner/builtin/git_commiter_test.go
@@ -68,39 +68,83 @@ func Test_gitCommitter_validate(t *testing.T) {
 		},
 		{
 			name: "author email is not specified",
-			config: promotion.Config{ // Should be completely valid
-				"author":  promotion.Config{},
+			config: promotion.Config{ // If author is specified, email must be specified
+				"author":  promotion.Config{
+					"name": "Tony Stark",
+				},
 				"path":    "/tmp/foo",
 				"message": "fake commit message",
 			},
+			expectedProblems: []string{
+				"invalid git-commit config: author: email is required",
+			},
 		},
 		{
 			name: "author email is empty string",
-			config: promotion.Config{ // Should be completely valid
+			config: promotion.Config{
 				"author": promotion.Config{
+					"name":  "Tony Stark",
 					"email": "",
 				},
 				"path":    "/tmp/foo",
 				"message": "fake commit message",
 			},
+			expectedProblems: []string{
+				"invalid git-commit config: author.email: Does not match format 'email'",
+			},
 		},
 		{
 			name: "author name is not specified",
-			config: promotion.Config{ // Should be completely valid
-				"author":  promotion.Config{},
+			config: promotion.Config{ // If author is specified, name must be specified
+				"author":  promotion.Config{
+					"email": "example@example.com",
+				},
 				"path":    "/tmp/foo",
 				"message": "fake commit message",
 			},
+			expectedProblems: []string{
+				"invalid git-commit config: author: name is required",
+			},
 		},
 		{
 			name: "author name is empty string",
-			config: promotion.Config{ // Should be completely valid
+			config: promotion.Config{
 				"author": promotion.Config{
 					"name": "",
+					"email": "example@example.com",
+				},
+				"path":    "/tmp/foo",
+				"message": "fake commit message",
+			},
+			expectedProblems: []string{
+				"invalid git-commit config: author.name: String length must be greater than or equal to 1",
+			},
+		},
+		{
+			name: "author signingKey is empty string",
+			config: promotion.Config{
+				"author": promotion.Config{
+					"name":       "Tony Stark",
+					"email":      "tony@starkindustries.com",
+					"signingKey": "",
+				},
+				"path":    "/tmp/foo",
+				"message": "fake commit message",
+			},
+			// No expected problems because signingKey is optional
+		},
+		{
+			name: "author signingKey is missing",
+			config: promotion.Config{
+				"author": promotion.Config{
+					"name":  "Tony Stark",
+					"email": "tony@starkindustries.com",
+					// signingKey is absent
 				},
 				"path":    "/tmp/foo",
 				"message": "fake commit message",
 			},
+			// No expected problems because signingKey is optional
 		},
 		{
 			name: "valid kitchen sink",
@@ -108,6 +152,7 @@ func Test_gitCommitter_validate(t *testing.T) {
 				"author": promotion.Config{
 					"email": "tony@starkindustries.com",
 					"name":  "Tony Stark",
+					"signingKey": "valid-signing-key",
 				},
 				"path":    "/tmp/foo",
 				"message": "fake commit message",
@@ -200,7 +245,7 @@ func Test_gitCommitter_run(t *testing.T) {
 		},
 	)
 	require.NoError(t, err)
-	require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+	require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 	expectedCommit, err := workTree.LastCommitID()
 	require.NoError(t, err)
 	actualCommit, ok := res.Output[stateKeyCommit]
diff --git a/internal/promotion/runner/builtin/git_pr_waiter_test.go b/internal/promotion/runner/builtin/git_pr_waiter_test.go
index 7b33054d..4f78cc24 100644
--- a/internal/promotion/runner/builtin/git_pr_waiter_test.go
+++ b/internal/promotion/runner/builtin/git_pr_waiter_test.go
@@ -120,7 +120,7 @@ func Test_gitPRWaiter_run(t *testing.T) {
 			assertions: func(t *testing.T, res promoPkg.StepResult, err error) {
 				require.ErrorContains(t, err, "error getting pull request")
 				require.ErrorContains(t, err, "something went wrong")
-				require.Equal(t, kargoapi.PromotionPhaseErrored, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusErrored, res.Status)
 			},
 		},
 		{
@@ -137,7 +137,7 @@ func Test_gitPRWaiter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promoPkg.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseRunning, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusRunning, res.Status)
 			},
 		},
 		{
@@ -156,7 +156,7 @@ func Test_gitPRWaiter_run(t *testing.T) {
 			assertions: func(t *testing.T, res promoPkg.StepResult, err error) {
 				require.ErrorContains(t, err, "closed without being merged")
 				require.True(t, promotion.IsTerminal(err))
-				require.Equal(t, kargoapi.PromotionPhaseFailed, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusFailed, res.Status)
 			},
 		},
 		{
@@ -174,7 +174,7 @@ func Test_gitPRWaiter_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promoPkg.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 			},
 		},
 	}
diff --git a/internal/promotion/runner/builtin/git_tree_clearer_test.go b/internal/promotion/runner/builtin/git_tree_clearer_test.go
index d34edfc3..32e98852 100644
--- a/internal/promotion/runner/builtin/git_tree_clearer_test.go
+++ b/internal/promotion/runner/builtin/git_tree_clearer_test.go
@@ -120,7 +120,7 @@ func Test_gitTreeOverwriter_run(t *testing.T) {
 		},
 	)
 	require.NoError(t, err)
-	require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+	require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 
 	// Make sure old files are gone
 	_, err = os.Stat(filepath.Join(workTree.Dir(), "original.txt"))
diff --git a/internal/promotion/runner/builtin/helm_chart_updater_test.go b/internal/promotion/runner/builtin/helm_chart_updater_test.go
index 1b6751db..f414b508 100644
--- a/internal/promotion/runner/builtin/helm_chart_updater_test.go
+++ b/internal/promotion/runner/builtin/helm_chart_updater_test.go
@@ -228,7 +228,7 @@ func Test_helmChartUpdater_run(t *testing.T) {
 			assertions: func(t *testing.T, tempDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"commitMessage": `Updated chart dependencies for testchart
 
diff --git a/internal/promotion/runner/builtin/helm_template_runner_test.go b/internal/promotion/runner/builtin/helm_template_runner_test.go
index cbd6819a..ae9f0768 100644
--- a/internal/promotion/runner/builtin/helm_template_runner_test.go
+++ b/internal/promotion/runner/builtin/helm_template_runner_test.go
@@ -51,7 +51,7 @@ data:
 			},
 			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)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outPath := filepath.Join(workDir, "output.yaml")
 				require.FileExists(t, outPath)
@@ -97,7 +97,7 @@ data:
 			},
 			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)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outPath := filepath.Join(workDir, "output.yaml")
 				require.FileExists(t, outPath)
@@ -144,7 +144,7 @@ data:
 			},
 			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)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outPath := filepath.Join(workDir, "output.yaml")
 				require.FileExists(t, outPath)
@@ -189,7 +189,7 @@ data:
 			},
 			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)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				outPath := filepath.Join(workDir, "output", "test-chart")
 				require.DirExists(t, outPath)
@@ -215,7 +215,7 @@ data:
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "failed to compose values")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
@@ -227,7 +227,7 @@ data:
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "failed to load chart")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
@@ -250,7 +250,7 @@ dependencies:
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "missing chart dependencies")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
@@ -268,7 +268,7 @@ version: 0.1.0`,
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "failed to initialize Helm action config")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
@@ -294,7 +294,7 @@ data:
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "failed to render chart")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
@@ -319,7 +319,7 @@ metadata:
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "failed to write rendered chart")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				require.NoFileExists(t, filepath.Join(workDir, "output.yaml"))
 			},
diff --git a/internal/promotion/runner/builtin/http_requester_test.go b/internal/promotion/runner/builtin/http_requester_test.go
index 293960f4..4f8e0877 100644
--- a/internal/promotion/runner/builtin/http_requester_test.go
+++ b/internal/promotion/runner/builtin/http_requester_test.go
@@ -249,7 +249,7 @@ func Test_httpRequester_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 				require.Equal(
 					t,
 					map[string]any{
@@ -282,7 +282,7 @@ func Test_httpRequester_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 				require.Equal(
 					t,
 					map[string]any{
@@ -315,7 +315,7 @@ func Test_httpRequester_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseSucceeded, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)
 				require.Equal(
 					t,
 					map[string]any{
@@ -337,7 +337,7 @@ func Test_httpRequester_run(t *testing.T) {
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "HTTP (404) response met failure criteria")
 				require.True(t, promotion.IsTerminal(err))
-				require.Equal(t, kargoapi.PromotionPhaseFailed, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusFailed, res.Status)
 			},
 		},
 		{
@@ -350,7 +350,7 @@ func Test_httpRequester_run(t *testing.T) {
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "HTTP (200) response met failure criteria")
 				require.True(t, promotion.IsTerminal(err))
-				require.Equal(t, kargoapi.PromotionPhaseFailed, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusFailed, res.Status)
 			},
 		},
 		{
@@ -364,7 +364,7 @@ func Test_httpRequester_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, res promotion.StepResult, err error) {
 				require.NoError(t, err)
-				require.Equal(t, kargoapi.PromotionPhaseRunning, res.Status)
+				require.Equal(t, kargoapi.PromotionStepStatusRunning, res.Status)
 			},
 		},
 	}
diff --git a/internal/promotion/runner/builtin/json_parser_test.go b/internal/promotion/runner/builtin/json_parser_test.go
index e56c3a55..c8c1fb1b 100644
--- a/internal/promotion/runner/builtin/json_parser_test.go
+++ b/internal/promotion/runner/builtin/json_parser_test.go
@@ -158,7 +158,7 @@ func Test_jsonParser_run(t *testing.T) {
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"appVersion":    "1.0.0",
 						"featureStatus": false,
@@ -181,7 +181,7 @@ func Test_jsonParser_run(t *testing.T) {
 			files: map[string]string{"config.json": `{ "app": { "version": "1.0.0" }}`},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "failed to extract outputs")
 			},
 		},
@@ -204,7 +204,7 @@ func Test_jsonParser_run(t *testing.T) {
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseErrored,
+					Status: kargoapi.PromotionStepStatusErrored,
 				}, result)
 				assert.Contains(t, err.Error(), "outputs is required")
 			},
@@ -225,7 +225,7 @@ func Test_jsonParser_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "could not parse JSON file")
 			},
 		},
@@ -239,7 +239,7 @@ func Test_jsonParser_run(t *testing.T) {
 			files: map[string]string{},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "JSON file path cannot be empty")
 			},
 		},
@@ -252,7 +252,7 @@ func Test_jsonParser_run(t *testing.T) {
 			files: map[string]string{},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "no such file or directory")
 			},
 		},
@@ -285,7 +285,7 @@ func Test_jsonParser_run(t *testing.T) {
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"appVersion": "2.0.1",
 						"isEnabled":  true,
diff --git a/internal/promotion/runner/builtin/json_updater_test.go b/internal/promotion/runner/builtin/json_updater_test.go
index 88eb17e5..f29c013c 100644
--- a/internal/promotion/runner/builtin/json_updater_test.go
+++ b/internal/promotion/runner/builtin/json_updater_test.go
@@ -320,7 +320,7 @@ func Test_jsonUpdater_run(t *testing.T) {
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"commitMessage": "Updated config.json\n\n" +
 							"- app.version: \"1.0.1\"\n" +
@@ -348,7 +348,7 @@ func Test_jsonUpdater_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "JSON file update failed")
 			},
 		},
@@ -371,7 +371,7 @@ func Test_jsonUpdater_run(t *testing.T) {
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 				}, result)
 				content, err := os.ReadFile(path.Join(workDir, "config.json"))
 				require.NoError(t, err)
@@ -440,7 +440,7 @@ func Test_jsonUpdater_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.Status)
 				content, err := os.ReadFile(path.Join(workDir, "config.json"))
 				require.NoError(t, err)
 				assert.JSONEq(t, `{"threshold": 425}`, string(content))
@@ -462,7 +462,7 @@ func Test_jsonUpdater_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.Status)
 				content, err := os.ReadFile(path.Join(workDir, "config.json"))
 				require.NoError(t, err)
 				assert.JSONEq(t, `{"features": {"existingFeature": false}}`, string(content))
diff --git a/internal/promotion/runner/builtin/kustomize_builder_test.go b/internal/promotion/runner/builtin/kustomize_builder_test.go
index f63ae36b..836a6c61 100644
--- a/internal/promotion/runner/builtin/kustomize_builder_test.go
+++ b/internal/promotion/runner/builtin/kustomize_builder_test.go
@@ -46,7 +46,7 @@ metadata:
 			},
 			assertions: func(t *testing.T, dir string, result promotion.StepResult, err error) {
 				require.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				assert.FileExists(t, filepath.Join(dir, "output.yaml"))
 				b, err := os.ReadFile(filepath.Join(dir, "output.yaml"))
@@ -97,7 +97,7 @@ replicaCount: 3`), 0o600))
 			},
 			assertions: func(t *testing.T, dir string, result promotion.StepResult, err error) {
 				require.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				assert.FileExists(t, filepath.Join(dir, "output.yaml"))
 				b, err := os.ReadFile(filepath.Join(dir, "output.yaml"))
@@ -133,7 +133,7 @@ metadata:
 			},
 			assertions: func(t *testing.T, dir string, result promotion.StepResult, err error) {
 				require.NoError(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded}, result)
 
 				assert.DirExists(t, filepath.Join(dir, "output"))
 				b, err := os.ReadFile(filepath.Join(dir, "output", "deployment-test-deployment.yaml"))
@@ -150,7 +150,7 @@ metadata:
 			},
 			assertions: func(t *testing.T, dir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "no such file or directory")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				assert.NoFileExists(t, filepath.Join(dir, "output.yaml"))
 			},
@@ -166,7 +166,7 @@ metadata:
 			},
 			assertions: func(t *testing.T, dir string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "invalid Kustomization")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 
 				assert.NoFileExists(t, filepath.Join(dir, "output.yaml"))
 			},
diff --git a/internal/promotion/runner/builtin/kustomize_image_setter_test.go b/internal/promotion/runner/builtin/kustomize_image_setter_test.go
index 98045bc1..2882a699 100644
--- a/internal/promotion/runner/builtin/kustomize_image_setter_test.go
+++ b/internal/promotion/runner/builtin/kustomize_image_setter_test.go
@@ -197,7 +197,7 @@ kind: Kustomization
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"commitMessage": "Updated . to use new image\n\n- nginx:1.21.0",
 					},
@@ -236,7 +236,7 @@ kind: Kustomization
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				require.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"commitMessage": "Updated . to use new images\n\n- nginx@sha256:123\n- redis:6.2.5",
 					},
@@ -260,7 +260,7 @@ kind: Kustomization
 			stepCtx: &promotion.StepContext{Project: testNamespace},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				require.ErrorContains(t, err, "could not discover kustomization file:")
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 			},
 		},
 	}
diff --git a/internal/promotion/runner/builtin/yaml_parser_test.go b/internal/promotion/runner/builtin/yaml_parser_test.go
index 6821e529..e9fcd48e 100644
--- a/internal/promotion/runner/builtin/yaml_parser_test.go
+++ b/internal/promotion/runner/builtin/yaml_parser_test.go
@@ -156,7 +156,7 @@ features:
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"appVersion":    "1.0.0",
 						"featureStatus": false,
@@ -184,7 +184,7 @@ app:
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "failed to extract outputs")
 			},
 		},
@@ -206,7 +206,7 @@ app:
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseErrored,
+					Status: kargoapi.PromotionStepStatusErrored,
 				}, result)
 				assert.Contains(t, err.Error(), "outputs is required")
 			},
@@ -227,7 +227,7 @@ app:
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "could not parse empty YAML file")
 			},
 		},
@@ -241,7 +241,7 @@ app:
 			files: map[string]string{},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "YAML file path cannot be empty")
 			},
 		},
@@ -255,7 +255,7 @@ app:
 			files: map[string]string{},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "no such file or directory")
 			},
 		},
@@ -285,7 +285,7 @@ config:
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"appVersion": "2.0.1",
 						"isEnabled":  true,
diff --git a/internal/promotion/runner/builtin/yaml_updater_test.go b/internal/promotion/runner/builtin/yaml_updater_test.go
index 6f203bf4..8d88d4b7 100644
--- a/internal/promotion/runner/builtin/yaml_updater_test.go
+++ b/internal/promotion/runner/builtin/yaml_updater_test.go
@@ -143,7 +143,7 @@ func Test_yamlUpdater_run(t *testing.T) {
 			assertions: func(t *testing.T, workDir string, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
 				assert.Equal(t, promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 					Output: map[string]any{
 						"commitMessage": "Updated values.yaml\n\n- image.tag: \"fake-tag\"",
 					},
@@ -166,7 +166,7 @@ func Test_yamlUpdater_run(t *testing.T) {
 			},
 			assertions: func(t *testing.T, _ string, result promotion.StepResult, err error) {
 				assert.Error(t, err)
-				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, result)
+				assert.Equal(t, promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, result)
 				assert.Contains(t, err.Error(), "values file update failed")
 			},
 		},
diff --git a/internal/promotion/simple_engine_test.go b/internal/promotion/simple_engine_test.go
index f2a1a6bb..b6476767 100644
--- a/internal/promotion/simple_engine_test.go
+++ b/internal/promotion/simple_engine_test.go
@@ -71,7 +71,7 @@ func TestSimpleEngine_Promote(t *testing.T) {
 				assert.ErrorContains(t, err, "met error threshold")
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status)
 				assert.Contains(t, result.StepExecutionMetadata[0].Message, context.Canceled.Error())
 			},
 		},
@@ -105,13 +105,13 @@ func TestSimpleEngine_Promote(t *testing.T) {
 			testRegistry.register(
 				&mockStepRunner{
 					name:      "success-step",
-					runResult: promotion.StepResult{Status: kargoapi.PromotionPhaseSucceeded},
+					runResult: promotion.StepResult{Status: kargoapi.PromotionStepStatusSucceeded},
 				},
 			)
 			testRegistry.register(
 				&mockStepRunner{
 					name:      "error-step",
-					runResult: promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+					runResult: promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 					runErr:    errors.New("something went wrong"),
 				},
 			)
@@ -121,7 +121,7 @@ func TestSimpleEngine_Promote(t *testing.T) {
 					runFunc: func(ctx context.Context, _ *promotion.StepContext) (promotion.StepResult, error) {
 						cancel() // Cancel context immediately
 						<-ctx.Done()
-						return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, ctx.Err()
+						return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, ctx.Err()
 					},
 				},
 			)
@@ -181,7 +181,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				// Verify the result contains metadata from both steps
 				assert.Len(t, result.StepExecutionMetadata, 2)
 				for _, metadata := range result.StepExecutionMetadata {
-					assert.Equal(t, kargoapi.PromotionPhaseSucceeded, metadata.Status)
+					assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, metadata.Status)
 					assert.NotNil(t, metadata.StartedAt)
 					assert.NotNil(t, metadata.FinishedAt)
 				}
@@ -212,12 +212,13 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				// Verify the result contains metadata from all steps
 				assert.Len(t, result.StepExecutionMetadata, 3)
 				for _, metadata := range result.StepExecutionMetadata {
-					assert.Equal(t, kargoapi.PromotionPhaseSucceeded, metadata.Status)
 					switch metadata.Alias {
 					case "step2":
+						assert.Equal(t, kargoapi.PromotionStepStatusSkipped, metadata.Status)
 						assert.Nil(t, metadata.StartedAt)
 						assert.Nil(t, metadata.FinishedAt)
 					default:
+						assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, metadata.Status)
 						assert.NotNil(t, metadata.StartedAt)
 						assert.NotNil(t, metadata.FinishedAt)
 					}
@@ -258,7 +259,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Len(t, result.StepExecutionMetadata, 2)
 				// We're not bothering with assertions on the dummy metadata for the 0
 				// step.
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.StepExecutionMetadata[1].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[1].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[1].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[1].FinishedAt)
 
@@ -281,10 +282,10 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Equal(t, int64(1), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 2)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[1].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[1].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[1].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[1].FinishedAt)
 				assert.Contains(t, result.StepExecutionMetadata[1].Message, "something went wrong")
@@ -309,10 +310,10 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Equal(t, int64(1), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 2)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[1].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[1].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[1].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[1].FinishedAt)
 				assert.Contains(t, result.StepExecutionMetadata[1].Message, "something went wrong")
@@ -340,7 +341,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseRunning, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.Nil(t, result.StepExecutionMetadata[0].FinishedAt)
 				assert.Equal(t, uint32(1), result.StepExecutionMetadata[0].ErrorCount)
@@ -372,7 +373,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
 				assert.Equal(t, uint32(1), result.StepExecutionMetadata[0].ErrorCount)
@@ -402,7 +403,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseRunning, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusRunning, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
 			},
@@ -415,7 +416,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseRunning, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseRunning, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusRunning, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.Nil(t, result.StepExecutionMetadata[0].FinishedAt)
 			},
@@ -428,7 +429,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status)
 				assert.Contains(t, result.StepExecutionMetadata[0].Message, context.Canceled.Error())
 			},
 		},
@@ -438,7 +439,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				&mockStepRunner{
 					name: ComposeOutputStepKind,
 					runResult: promotion.StepResult{
-						Status: kargoapi.PromotionPhaseSucceeded,
+						Status: kargoapi.PromotionStepStatusSucceeded,
 						Output: map[string]any{"test": "value"},
 					},
 				},
@@ -452,7 +453,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
 				assert.Equal(t, promotion.State{
@@ -473,7 +474,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				&mockStepRunner{
 					name: ComposeOutputStepKind,
 					runResult: promotion.StepResult{
-						Status: kargoapi.PromotionPhaseSucceeded,
+						Status: kargoapi.PromotionStepStatusSucceeded,
 						Output: map[string]any{"test": "value"},
 					},
 				},
@@ -487,7 +488,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status)
 				assert.Equal(t, int64(0), result.CurrentStep)
 				assert.Len(t, result.StepExecutionMetadata, 1)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.StepExecutionMetadata[0].Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[0].Status)
 				assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt)
 				assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt)
 				assert.Equal(t, promotion.State{
@@ -510,24 +511,24 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 				&mockStepRunner{
 					name: "success-step",
 					runResult: promotion.StepResult{
-						Status: kargoapi.PromotionPhaseSucceeded,
+						Status: kargoapi.PromotionStepStatusSucceeded,
 						Output: map[string]any{"key": "value"},
 					},
 				},
 				&mockStepRunner{
 					name: "running-step",
 					runResult: promotion.StepResult{
-						Status: kargoapi.PromotionPhaseRunning,
+						Status: kargoapi.PromotionStepStatusRunning,
 					},
 				},
 				&mockStepRunner{
 					name:      "error-step",
-					runResult: promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+					runResult: promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 					runErr:    errors.New("something went wrong"),
 				},
 				&mockStepRunner{
 					name:      "terminal-error-step",
-					runResult: promotion.StepResult{Status: kargoapi.PromotionPhaseErrored},
+					runResult: promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
 					runErr:    &promotion.TerminalError{Err: errors.New("something went wrong")},
 				},
 				&mockStepRunner{
@@ -535,7 +536,7 @@ func TestSimpleEngine_executeSteps(t *testing.T) {
 					runFunc: func(ctx context.Context, _ *promotion.StepContext) (promotion.StepResult, error) {
 						cancel()
 						<-ctx.Done()
-						return promotion.StepResult{Status: kargoapi.PromotionPhaseErrored}, ctx.Err()
+						return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored}, ctx.Err()
 					},
 				},
 			}
@@ -567,12 +568,12 @@ func TestSimpleEngine_executeStep(t *testing.T) {
 			runner: &mockStepRunner{
 				name: "success-step",
 				runResult: promotion.StepResult{
-					Status: kargoapi.PromotionPhaseSucceeded,
+					Status: kargoapi.PromotionStepStatusSucceeded,
 				},
 			},
 			assertions: func(t *testing.T, result promotion.StepResult, err error) {
 				assert.NoError(t, err)
-				assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.Status)
 			},
 		},
 		{
@@ -581,14 +582,14 @@ func TestSimpleEngine_executeStep(t *testing.T) {
 			runner: &mockStepRunner{
 				name: "error-step",
 				runResult: promotion.StepResult{
-					Status: kargoapi.PromotionPhaseErrored,
+					Status: kargoapi.PromotionStepStatusErrored,
 				},
 				runErr: errors.New("something went wrong"),
 			},
 			assertions: func(t *testing.T, result promotion.StepResult, err error) {
 				assert.ErrorContains(t, err, "failed to run step \"error-step\"")
 				assert.ErrorContains(t, err, "something went wrong")
-				assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status)
+				assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.Status)
 			},
 		},
 	}