diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/15-promotion-templates.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/15-promotion-templates.md
new file mode 100644
index 000000000..56ef8035f
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/15-promotion-templates.md
@@ -0,0 +1,9 @@
+---
+sidebar_label: Promotion Templates
+---
+
+# Promotion Templates Reference
+
+:::warning
+Hidde to put something here by the end of the week
+:::
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/20-expressions.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/20-expressions.md
index 4306de872..86d3df589 100644
--- a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/20-expressions.md
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/20-expressions.md
@@ -2,7 +2,7 @@
 sidebar_label: Expression Language
 ---
 
-# Kargo's Expression Language Reference
+# Expression Language Reference
 
 :::warning
 Hidde to put something here by the end of the week
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/10-git-clone.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/10-git-clone.md
new file mode 100644
index 000000000..2d74a5c39
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/10-git-clone.md
@@ -0,0 +1,100 @@
+---
+sidebar_label: git-clone
+description: Clones a remote Git repository and checks out specified revisions to working trees at specified paths.
+---
+
+# `git-clone`
+
+`git-clone` is often the first step in a promotion process. It creates a
+[bare clone](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--barecode)
+of a remote Git repository, then checks out one or more branches, tags, or
+commits to working trees at specified paths. Checking out different revisions to
+different paths is useful for the common scenarios of combining content from
+multiple sources or rendering Stage-specific manifests to a Stage-specific
+branch.
+
+:::note
+It is a noteworthy limitation of Git that one branch cannot be checked out in
+multiple working trees.
+:::
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `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. |
+| `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`, `tag`, and `fromFreight=true`. 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. |
+| `checkout[].commit` | `string` | N | A specific commit to check out. Mutually exclusive with `branch`, `tag`, and `fromFreight=true`. If none of these is specified, the default branch will be checked out. |
+| `checkout[].tag` | `string` | N | A tag to check out. Mutually exclusive with `branch`, `commit`, and `fromFreight=true`. If none of these is specified, the default branch will be checked out. |
+| `checkout[].fromFreight` | `boolean` | N | Whether a commit to check out should be obtained from the Freight being promoted. A value of `true` is mutually exclusive with `branch`, `commit`, and `tag`. If none of these is specified, the default branch will be checked out. Default is `false`, but is often set to `true`. <br/><br/>__Deprecated: Use `commit` with an expression instead. Will be removed in v1.3.0.__ |
+| `checkout[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). <br/><br/>__Deprecated: Use `commit` with an expression instead. Will be removed in v1.3.0.__ |
+| `checkout[].path` | `string` | Y | The path for a working tree that will be created from the checked out revision. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+
+## Examples
+
+### Common Usage
+
+The most common usage of this step is to check out a commit specified by the
+Freight being promoted as well as a Stage-specific branch. Subsequent steps are
+likely to perform actions that revise the contents of the Stage-specific branch
+using the commit from the Freight as input.
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo) }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+# Prepare the contents of ./out ...
+# Commit, push, etc...
+```
+
+### Combining Multiple Sources
+
+For this more advanced example, consider a Stage that requests Freight from two
+Warehouses, where one provides Kustomize "base" configuration, while the other
+provides a Stage-specific Kustomize overlay. Rendering the manifests intended
+for such a Stage will require combining the base and overlay configurations
+with the help of a [`copy`](20-copy.md) step. For this case, a `git-clone` step
+may be configured similarly to the following:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo, warehouse("base")).ID }}
+      path: ./src
+    - commit: ${{ commitFrom(vars.gitRepo, warehouse(ctx.stage + "-overlay")).ID }}
+      path: ./overlay
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: copy
+  config:
+    inPath: ./overlay/stages/${{ ctx.stage }}/kustomization.yaml
+    outPath: ./src/stages/${{ ctx.stage }}/kustomization.yaml
+- uses: kustomize-build
+  config:
+    path: ./src/stages/${{ ctx.stage }}
+    outPath: ./out
+# Commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/11-git-clear.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/11-git-clear.md
new file mode 100644
index 000000000..07642b7d1
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/11-git-clear.md
@@ -0,0 +1,37 @@
+---
+sidebar_label: git-clear
+description: Deletes the entire contents of a specified Git working tree.
+---
+
+# `git-clear`
+
+`git-clear` deletes _the entire contents_ of a specified Git working tree
+(except for the `.git` file). It is equivalent to executing
+`git add . && git rm -rf --ignore-unmatch .`. This step is useful for the common
+scenario where the entire content of a Stage-specific branch is to be replaced
+with content from another branch or with content rendered using some
+configuration management tool.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a Git working tree whose entire contents are to be deleted. |
+
+## Examples
+
+```yaml
+steps:
+- uses: git-clone
+  config:
+    repoURL: https://github.com/example/repo.git
+    checkout:
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+# Prepare the contents of ./out ...
+# Commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/15-git-commit.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/15-git-commit.md
new file mode 100644
index 000000000..d894ed151
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/15-git-commit.md
@@ -0,0 +1,63 @@
+---
+sidebar_label: git-commit
+description: Commits all changes in a working tree to its checked out branch.
+---
+
+# `git-commit`
+
+`git-commit` commits all changes in a working tree to its checked out branch.
+This step is often used after previous steps have put the working tree into the
+desired state and is commonly followed by a [`git-push` step](16-git-push.md).
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `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` | N | The commit message. Mutually exclusive with `messageFromSteps`. |
+| `messageFromSteps` | `[]string` | N | References the `commitMessage` output of previous steps. When one or more are specified, the commit message will be constructed by concatenating the messages from individual steps. Mutually exclusive with `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. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commit` | `string` | The ID (SHA) of the commit created by this step. If the step short-circuited and did not create a new commit because there were no differences from the current head of the branch, this value will be the ID of the existing commit at the head of the branch instead. Typically, a subsequent [`argocd-update`](50-argocd-update.md) step will reference this output to learn the ID of the commit that an applicable Argo CD `ApplicationSource` should be observably synced to under healthy conditions. |
+
+## Examples
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: kustomize-set-image
+  as: update-image
+  config:
+    images:
+    - image: my/image
+- uses: kustomize-build
+  config:
+    path: ./src/stages/${{ ctx.stage }}
+    outPath: ./out
+- uses: git-commit
+  config:
+    path: ./out
+    messageFromSteps:
+    - update-image
+# Push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/16-git-push.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/16-git-push.md
new file mode 100644
index 000000000..b44db4422
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/16-git-push.md
@@ -0,0 +1,76 @@
+---
+sidebar_label: git-push
+description: Pushes the committed changes in a specified working tree to a specified branch in the remote repository.
+---
+
+# `git-push`
+
+`git-push` pushes the committed changes in a specified working tree to a
+specified branch in the remote repository. This step typically follows a
+[`git-commit` step](15-git-commit.md) and is often followed by a
+[`git-open-pr` step](18-git-open-pr.md).
+
+This step also implements its own, internal retry logic. If a push fails, with
+the cause determined to be the presence of new commits in the remote branch that
+are not present in the local branch, the step will attempt to rebase before
+retrying the push. Any merge conflict requiring manual resolution will
+immediately halt further attempts.
+
+:::info
+This step's internal retry logic is helpful in scenarios when concurrent
+Promotions to multiple Stages may all write to the same branch of the same
+repository.
+
+Because conflicts requiring manual resolution will halt further attempts, it is
+recommended to design your Promotion processes such that Promotions to multiple
+Stages that write to the same branch do not write to the same files.
+:::
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a Git working tree containing committed changes. |
+| `targetBranch` | `string` | N | The branch to push to in the remote repository. Mutually exclusive with `generateTargetBranch=true`. If neither of these is provided, the target branch will be the same as the branch currently checked out in the working tree. |
+| `maxAttempts` | `int32` | N | The maximum number of attempts to make when pushing to the remote repository. Default is 50. |
+| `generateTargetBranch` | `boolean` | N | Whether to push to a remote branch named like `kargo/<project>/<stage>/promotion`. If such a branch does not already exist, it will be created. A value of 'true' is mutually exclusive with `targetBranch`. If neither of these is provided, the target branch will be the currently checked out branch. This option is useful when a subsequent promotion step will open a pull request against a Stage-specific branch. In such a case, the generated target branch pushed to by the `git-push` step can later be utilized as the source branch of the pull request. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `branch` | `string` | The name of the remote branch pushed to by this step. This is especially useful when the `generateTargetBranch=true` option has been used, in which case a subsequent [`git-open-pr`](18-git-open-pr.md) will typically reference this output to learn what branch to use as the head branch of a new pull request. |
+| `commit` | `string` | The ID (SHA) of the commit pushed by this step. |
+
+## Examples
+
+### Common Usage
+
+```yaml
+steps:
+# Clone, prepare the contents of ./out, etc...
+- uses: git-commit
+  config:
+    path: ./out
+    message: rendered updated manifests
+- uses: git-push
+  config:
+    path: ./out
+```
+
+### For Use With a Pull Request
+
+```yaml
+steps:
+# Clone, prepare the contents of ./out, etc...
+- uses: git-commit
+  config:
+    path: ./out
+    message: rendered updated manifests
+- uses: git-push
+  as: push
+  config:
+    path: ./out
+    generateTargetBranch: true
+# Open a PR and wait for it to be merged or closed...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/18-git-open-pr.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/18-git-open-pr.md
new file mode 100644
index 000000000..077d0089e
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/18-git-open-pr.md
@@ -0,0 +1,54 @@
+---
+sidebar_label: git-open-pr
+description: Opens a pull request in a specified remote repository using specified source and target branches.
+---
+
+# `git-open-pr`
+
+`git-open-pr` opens a pull request in a specified remote repository using
+specified source and target branches. This step is often used after a
+[`git-push` step](16-git-push.md) and is commonly followed by a
+[`git-wait-for-pr` step](19-git-wait-for-pr.md).
+
+At present, this feature only supports GitHub pull requests and GitLab merge
+requests.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `repoURL` | `string` | Y | The URL of a remote Git repository. |
+| `provider` | `string` | N | The name of the Git provider to use. Currently only `github` and `gitlab` are supported. Kargo will try to infer the provider if it is not explicitly specified.  |
+| `insecureSkipTLSVerify` | `boolean` | N | Indicates whether to bypass TLS certificate verification when interfacing with the Git provider. Setting this to `true` is highly discouraged in production. |
+| `sourceBranch` | `string` | N | Specifies the source branch for the pull request. Mutually exclusive with `sourceBranchFromStep`. |
+| `sourceBranchFromStep` | `string` | N | Indicates the source branch should be determined by the `branch` key in the output of a previous promotion step with the specified alias. Mutually exclusive with `sourceBranch`.<br/><br/>__Deprecated: Use `sourceBranch` with an expression instead. Will be removed in v1.3.0.__  |
+| `targetBranch` | `string` | N | The branch to which the changes should be merged. |
+| `createTargetBranch` | `boolean` | N | Indicates whether a new, empty orphaned branch should be created and pushed to the remote if the target branch does not already exist there. Default is `false`. |
+| `title` | `string` | N | The title for the pull request. Kargo generates a title based on the commit messages if it is not explicitly specified. |
+| `labels` | `[]string` | N | Labels to add to the pull request. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `prNumber` | `number` | The numeric identifier of the pull request opened by this step. Typically, a subsequent [`git-wait-for-pr` step](19-git-wait-for-pr.md) will reference this output to learn what pull request to monitor. |
+
+## Examples
+
+```yaml
+steps:
+# Clone, prepare the contents of ./out, commit, etc...
+- uses: git-push
+  as: push
+  config:
+    path: ./out
+    generateTargetBranch: true
+- uses: git-open-pr
+  as: open-pr
+  config:
+    repoURL: https://github.com/example/repo.git
+    createTargetBranch: true
+    sourceBranch: ${{ outputs.push.branch }}
+    targetBranch: stage/${{ ctx.stage }}
+# Wait for the PR to be merged or closed...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/19-git-wait-for-pr.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/19-git-wait-for-pr.md
new file mode 100644
index 000000000..59ec74695
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/19-git-wait-for-pr.md
@@ -0,0 +1,50 @@
+---
+sidebar_label: git-wait-for-pr
+description: Waits for a specified open pull request to be merged or closed.
+---
+
+# `git-wait-for-pr`
+
+`git-wait-for-pr` waits for a specified open pull request to be merged or
+closed. This step commonly follows a [`git-open-pr` step](18-git-open-pr.md)
+and is commonly followed by an `argocd-update` step.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `repoURL` | `string` | Y | The URL of a remote Git repository. |
+| `provider` | `string` | N | The name of the Git provider to use. Currently only `github` and `gitlab` are supported. Kargo will try to infer the provider if it is not explicitly specified.  |
+| `insecureSkipTLSVerify` | `boolean` | N | Indicates whether to bypass TLS certificate verification when interfacing with the Git provider. Setting this to `true` is highly discouraged in production. |
+| `prNumber` | `string` | N | The number of the pull request to wait for. Mutually exclusive with `prNumberFromStep`. |
+| `prNumberFromStep` | `string` | N | References the `prNumber` output from a previous step. Mutually exclusive with `prNumber`.<br/><br/>__Deprecated: Use `prNumber` with an expression instead. Will be removed in v1.3.0.__ |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commit` | `string` | The ID (SHA) of the new commit at the head of the target branch after merge. Typically, a subsequent [`argocd-update` step](50-argocd-update.md) will reference this output to learn the ID of the commit that an applicable Argo CD `ApplicationSource` should be observably synced to under healthy conditions. |
+
+## Examples
+
+```yaml
+steps:
+# Clone, prepare the contents of ./out, commit, etc...
+- uses: git-push
+  as: push
+  config:
+    path: ./out
+    generateTargetBranch: true
+- uses: git-open-pr
+  as: open-pr
+  config:
+    repoURL: https://github.com/example/repo.git
+    createTargetBranch: true
+    sourceBranch: ${{ outputs.push.branch }}
+    targetBranch: stage/${{ ctx.stage }}
+- uses: git-wait-for-pr
+  as: wait-for-pr
+  config:
+    repoURL: https://github.com/example/repo.git
+    prNumber: ${{ outputs['open-pr'].prNumber }}
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/20-copy.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/20-copy.md
new file mode 100644
index 000000000..39f772e14
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/20-copy.md
@@ -0,0 +1,53 @@
+---
+sidebar_label: copy
+description: Copies files or the contents of entire directories from one specified location to another.
+---
+
+# `copy`
+
+`copy` copies files or the contents of entire directories from one specified
+location to another.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `inPath` | `string` | Y | Path to the file or directory to be copied. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `outPath` | `string` | Y | Path to the destination. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+
+## Examples
+
+The most common (though still advanced) usage of this step is to combine content
+from two working trees to use as input to a subsequent step, such as one that
+renders Stage-specific manifests.
+
+Consider a Stage that requests Freight from two Warehouses, where one provides
+Kustomize "base" configuration, while the other provides a Stage-specific
+Kustomize overlay. Rendering the manifests intended for such a Stage will
+require combining the base and overlay configurations:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo, warehouse("base")).ID }}
+      path: ./src
+    - commit: ${{ commitFrom(vars.gitRepo, warehouse(ctx.stage + "-overlay")).ID }}
+      path: ./overlay
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: copy
+  config:
+    inPath: ./overlay/stages/${{ ctx.stage }}/kustomization.yaml
+    outPath: ./src/stages/${{ ctx.stage }}/kustomization.yaml
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/21-delete.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/21-delete.md
new file mode 100644
index 000000000..ba8a3a59d
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/21-delete.md
@@ -0,0 +1,44 @@
+---
+sidebar_title: delete
+description: Deletes a file or directory.
+---
+
+# `delete`
+
+`delete` deletes a file or directory.
+
+## Configuration
+
+| Name      | Type | Required | Description                              |
+|-----------|------|----------|------------------------------------------|
+| `path`    | `string` | Y | Path to the file or directory to delete. |
+
+## Examples
+
+One common usage of this step is to remove intermediate files produced by the
+promotion process prior to a `git-commit` step:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo) }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+
+# Steps that produce intermediate files in ./out...
+
+- uses: delete
+  config:
+    path: ./out/unwanted/file
+- uses: git-commit
+  config:
+    path: ./out
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-json-update.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-json-update.md
new file mode 100644
index 000000000..1be4d7a0e
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-json-update.md
@@ -0,0 +1,51 @@
+---
+sidebar_label: json-update
+description: Updates the values of specified keys in any JSON file.
+---
+
+# `json-update`
+
+`json-update` updates the values of specified keys in any JSON file.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a JSON file. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |                      |
+| `updates` | `[]object` | Y | The details of changes to be applied to the file. At least one must be specified. |
+| `updates[].key` | `string` | Y | The key to update within the file. For nested values, use a JSON dot notation path. See [sjson documentation](https://github.com/tidwall/sjson) for supported syntax. |
+| `updates[].value`| `any` | Y | The new value for the key. Typically specified using an expression. Supports strings, numbers, booleans, arrays, and objects. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit` step](15-git-commit.md) reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |
+
+## Examples
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+    - uses: git-clear
+      config:
+        path: ./out
+- uses: json-update
+  config:
+    path: configs/settings.json
+    updates:
+    - key: image.tag
+      value: ${{ imageFrom("my/image").Tag }}
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-yaml-update.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-yaml-update.md
new file mode 100644
index 000000000..e48285e7d
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/25-yaml-update.md
@@ -0,0 +1,53 @@
+---
+sidebar_label: yaml-update
+description: Updates the values of specified keys in any YAML file.
+---
+
+# `yaml-update`
+
+`yaml-update` updates the values of specified keys in any YAML file. This step
+most often used to update image tags or digests in a Helm values and is commonly
+followed by a [`helm-template` step](49-helm-template.md).
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a YAML file. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `updates` | `[]object` | Y | The details of changes to be applied to the file. At least one must be specified. |
+| `updates[].key` | `string` | Y | The key to update within the file. For nested values, use dots to delimit key parts. e.g. `image.tag`. The syntax is identical to that supported by the `json-update` step and is documented in more detail [here](https://github.com/tidwall/sjson?tab=readme-ov-file#path-syntax). |
+| `updates[].value` | `string` | Y | The new value for the key. Typically specified using an expression. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit` step](15-git-commit.md) will reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |
+
+## Examples
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: yaml-update
+  config:
+    path: ./src/charts/my-chart/values.yaml
+    updates:
+    - key: image.tag
+      value: ${{ imageFrom("my/image").Tag }}
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/30-kustomize-set-image.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/30-kustomize-set-image.md
new file mode 100644
index 000000000..2d05f2e56
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/30-kustomize-set-image.md
@@ -0,0 +1,100 @@
+---
+sidebar_label: kustomize-set-image
+description: Updates the `kustomization.yaml` file in a specified directory to reflect a different revision of a container image.
+---
+
+# `kustomize-set-image`
+
+`kustomize-set-image` updates the `kustomization.yaml` file in a specified
+directory to reflect a different revision of a container image. It is equivalent
+to executing `kustomize edit set image`. This step is commonly followed by a
+[`kustomize-build` step](39-kustomize-build.md).
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a directory containing a `kustomization.yaml` file. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `images` | `[]object` | N | The details of changes to be applied to the `kustomization.yaml` file. When left unspecified, all images from the Freight collection will be set in the Kustomization file. Unless there is an ambiguous image name (for example, due to two Warehouses subscribing to the same repository), which requires manual configuration. |
+| `images[].image` | `string` | Y | Name/URL of the image being updated. |
+| `images[].tag` | `string` | N | A tag naming a specific revision of `image`. Mutually exclusive with `digest` and `useDigest=true`. If none of these are specified, the tag specified by a piece of Freight referencing `image` will be used as the value of this field. |
+| `images[].digest` | `string` | N | A digest naming a specific revision of `image`. Mutually exclusive with `tag` and `useDigest=true`. If none of these are specified, the tag specified by a piece of Freight referencing `image` will be used as the value of `tag`. |
+| `images[].useDigest` | `boolean` | N | Whether to update the `kustomization.yaml` file using the container image's digest instead of its tag. Mutually exclusive with `digest` and `tag`. If none of these are specified, the tag specified by a piece of Freight referencing `image` will be used as the value of `tag`. <br/><br/>__Deprecated: Use `digest` with an expression instead. Will be removed in v1.3.0.__ |
+| `images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). <br/><br/>__Deprecated: Use `digest` or `tag` with an expression instead. Will be removed in v1.3.0.__ |
+| `images[].newName` | `string` | N | A substitution for the name/URL of the image being updated. This is useful when different Stages have access to different container image repositories (assuming those different repositories contain equivalent images that are tagged identically). This may be a frequent consideration for users of Amazon's Elastic Container Registry. |
+
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit` step](15-git-commit.md) will reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |
+
+
+## Examples
+
+### Common Usage
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+- name: imageRepo
+  value: my/image
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: kustomize-set-image
+  config:
+    path: ./src/base
+    images:
+    - image: ${{ vars.imageRepo }}
+      tag: ${{ imageFrom(vars.imageRepo).Tag }}
+# Render manifests to ./out, commit, push, etc...
+```
+
+### Changing an Image Name
+
+For this example, consider the promotion of Freight containing a reference to
+some revision of the container image
+`123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image`. This image exists in the
+`us-east-1` region of Amazon's Elastic Container Registry. However, assuming the
+Stage targeted by the promotion is backed by environments in the `us-west-2`
+region, it will be necessary to make a substitution when updating the
+`kustomization.yaml` file. This can be accomplished like so:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: kustomize-set-image
+  config:
+    path: ./src/base
+    images:
+    - image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-image
+      newName: 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-image
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/39-kustomize-build.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/39-kustomize-build.md
new file mode 100644
index 000000000..5cb76a7af
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/39-kustomize-build.md
@@ -0,0 +1,76 @@
+---
+sidebar_label: kustomize-build
+description: Renders manifests from a specified directory containing a `kustomization.yaml` file to a specified file or to many files in a specified directory.
+---
+
+# `kustomize-build`
+
+`kustomize-build` renders manifests from a specified directory containing a
+`kustomization.yaml` file to a specified file or to many files in a specified
+directory. This step is useful for the common scenario of rendering
+Stage-specific manifests to a Stage-specific branch. This step is commonly
+preceded by a [`git-clear`](11-git-clear.md) step and followed by
+[`git-commit`](15-git-commit.md) and [`git-push`](16-git-push.md) steps.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a directory containing a `kustomization.yaml` file. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `outPath` | `string` | Y | Path to the file or directory where rendered manifests are to be written. If the path ends with `.yaml` or `.yml` it is presumed to indicate a file and is otherwise presumed to indicate a directory. This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `plugin.helm.apiVersions` | `[]string` | N | Optionally specifies a list of supported API versions to be used when rendering manifests using Kustomize's Helm chart plugin. This is useful for charts that may contain logic specific to different Kubernetes API versions. |
+| `plugin.helm.kubeVersion` | `string` | N | Optionally specifies a Kubernetes version to be assumed when rendering manifests using Kustomize's Helm chart plugin. This is useful for charts that may contain logic specific to different Kubernetes versions. |
+
+## Examples
+
+### Rendering to a File
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: kustomize-build
+  config:
+    path: ./src/stages/${{ ctx.stage }}
+    outPath: ./out/manifests.yaml
+# Commit, push, etc...
+```
+
+### Rendering to a Directory
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: kustomize-build
+  config:
+    path: ./src/stages/${{ ctx.stage }}
+    outPath: ./out
+# Commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/40-helm-update-image.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/40-helm-update-image.md
new file mode 100644
index 000000000..68dd65e26
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/40-helm-update-image.md
@@ -0,0 +1,61 @@
+---
+sidebar_label: helm-update-image
+description: Updates the values of specified keys in a specified Helm values file to reflect a new version of a container image.
+---
+
+# `helm-update-image`
+
+`helm-update-image` updates the values of specified keys in a specified Helm
+values file (e.g. `values.yaml`) to reflect a new version of a container image.
+This step is useful for the common scenario of updating such a `values.yaml`
+file with new version information which is referenced by the Freight being
+promoted. This step is commonly followed by a
+[`helm-template` step](49-helm-template.md).
+
+__Deprecated: Use the generic `yaml-update` step instead. Will be removed in v1.3.0.__
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to Helm values file (e.g. `values.yaml`). This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `images` | `[]object` | Y | The details of changes to be applied to the values file. At least one must be specified. |
+| `images[].image` | `string` | Y | Name/URL of the image being updated. The Freight being promoted presumably contains a reference to a revision of this image. |
+| `images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins) |
+| `images[].key` | `string` | Y | The key to update within the values file. See Helm documentation on the [format and limitations](https://helm.sh/docs/intro/using_helm/#the-format-and-limitations-of---set) of the notation used in this field. |
+| `images[].value` | `string` | Y | Specifies how the value of `key` is to be updated. Possible values for this field are limited to:<ul><li>`ImageAndTag`: Replaces the value of `key` with a string in form `<image url>:<tag>`</li><li>`Tag`: Replaces the value of `key` with the image's tag</li><li>`ImageAndDigest`: Replaces the value of `key` with a string in form `<image url>@<digest>`</li><li>`Digest`: Replaces the value of `key` with the image's digest</li></ul> |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit` step](15-git-commit.md) will reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |
+
+## Examples
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: helm-update-image
+  config:
+    path: ./src/charts/my-chart/values.yaml
+    images:
+    - image: my/image
+      key: image.tag
+      value: Tag
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/41-helm-update-chart.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/41-helm-update-chart.md
new file mode 100644
index 000000000..8e9fc9063
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/41-helm-update-chart.md
@@ -0,0 +1,183 @@
+---
+sidebar_label: helm-update-chart
+description: Updates the `dependencies` section of a specified Helm chart's `Chart.yaml` file.
+---
+
+# `helm-update-chart`
+
+`helm-update-chart` performs specified updates on the `dependencies` section of
+a specified Helm chart's `Chart.yaml` file. This step is useful for the common
+scenario of updating a chart's dependencies to reflect new versions of charts
+referenced by the Freight being promoted. This step is commonly followed by a
+[`helm-template` step](49-helm-template.md).
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a Helm chart (i.e. to a directory containing a `Chart.yaml` file). This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `charts` | `[]string` | Y | The details of dependency (subschart) updates to be applied to the chart's `Chart.yaml` file. |
+| `charts[].repository` | `string` | Y | The URL of the Helm chart repository in the `dependencies` entry whose `version` field is to be updated. Must _exactly_ match the `repository` field of that entry. |
+| `charts[].name` | `string` | Y | The name of the chart in in the `dependencies` entry whose `version` field is to be updated. Must exactly match the `name` field of that entry. |
+| `charts[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). <br/><br/>__Deprecated: Use `version` with an expression instead. Will be removed in v1.3.0.__ |
+| `charts[].version` | `string` | N | The version to which the dependency should be updated. If left unspecified, the version specified by a piece of Freight referencing this chart will be used. |
+
+## Output
+
+| Name | Type | Description |
+|------|------|-------------|
+| `commitMessage` | `string` | A description of the change(s) applied by this step. Typically, a subsequent [`git-commit` step](15-git-commit.md) will reference this output and aggregate this commit message fragment with other like it to build a comprehensive commit message that describes all changes. |
+
+## Examples
+
+### Classic Chart Repository
+
+Given a `Chart.yaml` file such as the following:
+
+```yaml
+apiVersion: v2
+name: example
+type: application
+version: 0.1.0
+appVersion: 0.1.0
+dependencies:
+- repository: https://example-chart-repo
+  name: some-chart
+  version: 1.2.3
+```
+
+The `dependencies` can be updated to reflect the version of `some-chart`
+referenced by the Freight being promoted like so:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+- name: chartRepo
+  value: https://example-chart-repo
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: helm-update-chart
+  config:
+    path: ./src/charts/my-chart
+    charts:
+    - repository: ${{ chartRepo }}
+      name: some-chart
+      version: ${{ chartFrom(chartRepo).Version }}
+# Render manifests to ./out, commit, push, etc...
+```
+
+### OCI Chart Repository
+
+:::caution
+Classic (HTTP/HTTPS) Helm chart repositories can contain many differently named
+charts. A specific chart, therefore, can be identified by a repository URL and
+a chart name.
+
+OCI repositories, on the other hand, are organizational constructs within OCI
+_registries._ Each OCI repository is presumed to contain versions of only a
+single chart. As such, a specific chart can be identified by a repository URL
+alone.
+
+Kargo Warehouses understand this distinction well, so a subscription to an OCI
+chart repository will utilize its URL only, _without_ specifying a chart name.
+For example:
+
+```yaml
+apiVersion: kargo.akuity.io/v1alpha1
+kind: Warehouse
+metadata:
+  name: my-warehouse
+  namespace: kargo-demo
+spec:
+  subscriptions:
+  - chart:
+      repoURL: oci://example-chart-registry/some-chart
+      semverConstraint: ^1.0.0
+```
+
+Helm deals with this difference somewhat more awkwardly, however. When a Helm
+chart references a chart in an OCI repository, it must reference the _registry_
+by URL in the `repository` field and _still_ specify a chart name in the name
+field. For example:
+
+```yaml
+apiVersion: v2
+name: example
+type: application
+version: 0.1.0
+appVersion: 0.1.0
+dependencies:
+- repository: oci://example-chart-registry
+  name: some-chart
+  version: 1.2.3
+```
+
+__When using `helm-update-chart` to update the dependencies in a `Chart.yaml`
+file, you must play by Helm's rules and use the _registry_ URL in the
+`repository` field and the _repository_ name (chart name) in the `name` field.__
+:::
+
+:::info
+As a general rule, when configuring Kargo to update something, observe the
+conventions of the thing being updated, even if those conventions differ from
+Kargo's own. Kargo is aware of such differences and will adapt accordingly.
+:::
+
+Given a `Chart.yaml` file such as the following:
+
+```yaml
+apiVersion: v2
+name: example
+type: application
+version: 0.1.0
+appVersion: 0.1.0
+dependencies:
+- repository: oci://example-chart-registry
+  name: some-chart
+  version: 1.2.3
+```
+
+The `dependencies` can be updated to reflect the version of
+`oci://example-chart-registry/some-chart` referenced by the Freight being
+promoted like so:
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+- name: chartReg
+  value: oci://example-chart-registry
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: helm-update-chart
+  config:
+    path: ./src/charts/my-chart
+    charts:
+    - repository: ${{ chartReg }}
+      name: some-chart
+      version: ${{ chartFrom(chartReg + "/some-chart").Version }}
+# Render manifests to ./out, commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/49-helm-template.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/49-helm-template.md
new file mode 100644
index 000000000..c088f6591
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/49-helm-template.md
@@ -0,0 +1,83 @@
+---
+sidebar_label: helm-template
+description: Renders a specified Helm chart to one or more files in a specified directory.
+---
+
+# `helm-template`
+
+`helm-template` renders a specified Helm chart to a specified directory or to
+many files in a specified directory. This step is useful for the common scenario
+of rendering Stage-specific manifests to a Stage-specific branch. This step is
+commonly preceded by a [`git-clear` step](11-git-clear.md) and followed by
+[`git-commit`](15-git-commit.md) and [`git-push`](16-git-push.md) steps.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `path` | `string` | Y | Path to a Helm chart (i.e. to a directory containing a `Chart.yaml` file). This path is relative to the temporary workspace that Kargo provisions for use by the promotion process. |
+| `outPath` | `string` | Y | Path to the file or directory where rendered manifests are to be written. If the path ends with `.yaml` or `.yml` it is presumed to indicate a file and is otherwise presumed to indicate a directory. |
+| `releaseName` | `string` | N | Optional release name to use when rendering the manifests. This is commonly omitted. |
+| `namespace` | `string` | N | Optional namespace to use when rendering the manifests. This is commonly omitted. GitOps agents such as Argo CD will generally ensure the installation of manifests into the namespace specified by their own configuration. |
+| `valuesFiles` | `[]string` | N | Helm values files (apart from the chart's default `values.yaml`) to be used when rendering the manifests.  |
+| `includeCRDs` | `boolean` | N | Whether to include CRDs in the rendered manifests. This is `false` by default. |
+| `kubeVersion` | `string` | N | Optionally specifies a Kubernetes version to be assumed when rendering manifests. This is useful for charts that may contain logic specific to different Kubernetes versions. |
+| `apiVersions` | `[]string` | N | Allows a manual set of supported API versions to be specified. |
+
+## Examples
+
+### Rendering to a File
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: helm-template
+  config:
+    path: ./src/charts/my-chart
+    valuesFiles:
+    - ./src/charts/my-chart/${{ ctx.stage }}-values.yaml
+    outPath: ./out/manifests.yaml
+# Commit, push, etc...
+```
+
+### Rendering to a Directory
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: git-clone
+  config:
+    repoURL: ${{ vars.gitRepo }}
+    checkout:
+    - commit: ${{ commitFrom(vars.gitRepo).ID }}
+      path: ./src
+    - branch: stage/${{ ctx.stage }}
+      create: true
+      path: ./out
+- uses: git-clear
+  config:
+    path: ./out
+- uses: helm-template
+  config:
+    path: ./src/charts/my-chart
+    valuesFiles:
+    - ./src/charts/my-chart/${{ ctx.stage }}-values.yaml
+    outPath: ./out
+# Commit, push, etc...
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/50-argocd-update.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/50-argocd-update.md
new file mode 100644
index 000000000..818e336f9
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/50-argocd-update.md
@@ -0,0 +1,211 @@
+---
+sidebar_label: argocd-update
+description: Updates one or more Argo CD `Application` resources in various ways.
+---
+
+# `argocd-update`
+
+`argocd-update` updates one or more Argo CD `Application` resources in various
+ways. Among other scenarios, this step is useful for the common one of forcing
+an Argo CD `Application` to sync after previous steps have updated a remote
+branch referenced by the `Application`. This step is commonly the last step in
+a promotion process.
+
+:::note
+For an Argo CD `Application` resource to be managed by a Kargo `Stage`,
+the `Application` _must_ have an annotation of the following form:
+
+```yaml
+kargo.akuity.io/authorized-stage: "<project-name>:<stage-name>"
+```
+
+Such an annotation offers proof that a user who is themselves authorized
+to update the `Application` in question has consented to a specific
+`Stage` updating the `Application` as well.
+
+The following example shows how to configure an Argo CD `Application`
+manifest to authorize the `test` `Stage` of the `kargo-demo` `Project`:
+
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+  name: kargo-demo-test
+  namespace: argocd
+  annotations:
+    kargo.akuity.io/authorized-stage: kargo-demo:test
+spec:
+  # Application specifications go here
+```
+:::
+
+:::info
+Enforcement of Argo CD
+[sync windows](https://argo-cd.readthedocs.io/en/stable/user-guide/sync_windows/)
+was improved substantially in Argo CD v2.11.0. If you wish for the `argocd-update`
+step to honor sync windows, you must use Argo CD v2.11.0 or later.
+
+_Additionally, it is recommended that if a promotion process is expected to
+sometimes encounter an active deny window, the `argocd-update` step should be
+configured with a timeout that is at least as long as the longest expected deny
+window. (The step's default timeout is five minutes.)_
+:::
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `apps` | `[]object` | Y | Describes Argo CD `Application` resources to update and how to update them. At least one must be specified.  |
+| `apps[].name` | `string` | Y | The name of the Argo CD `Application`. __Note:__ A small technical restriction on this field is that any [expressions](../20-expressions.md) used therein are limited to accessing `ctx` and `vars` and may not access `secrets` or any Freight. This is because templates in this field are, at times, evaluated outside the context of an actual `Promotion` for the purposes of building an index. In practice, this restriction does not prove to be especially limiting. |
+| `apps[].namespace` | `string` | N | The namespace of the Argo CD `Application` resource to be updated. If left unspecified, the namespace will be the Kargo controller's configured default -- typically `argocd`. __Note:__ This field is subject to the same restrictions as the `name` field. See above. |
+| `apps[].sources` | `[]object` | N | Describes Argo CD `ApplicationSource`s to update and how to update them. |
+| `apps[].sources[].repoURL` | `string` | Y | The value of the target `ApplicationSource`'s  own `repoURL` field. This must match exactly. |
+| `apps[].sources[].chart` | `string` | N | Applicable only when the target `ApplicationSource` references a Helm chart repository, the value of the target `ApplicationSource`'s  own `chart` field. This must match exactly. |
+| `apps[].sources[].desiredRevision` | `string` | N | Specifies the desired revision for the source. i.e. The revision to which the source must be observably synced when performing a health check. This field is mutually exclusive with `desiredCommitFromStep`. Prior to v1.1.0, if both were left undefined, the desired revision was determined by Freight (if possible). Beginning with v1.1.0, if both are left undefined, Kargo will not require the source to be observably synced to any particular source to be considered healthy. Note that the source's `targetRevision` will not be updated to this revision unless `updateTargetRevision=true` is also set. |
+| `apps[].sources[].desiredCommitFromStep` | `string` | N | Applicable only when `repoURL` references a Git repository, this field references the `commit` output from a previous step and uses it as the desired revision for the source. i.e. The revision to which the source must be observably synced when performing a health check. This field is mutually exclusive with `desiredRevisionFromStep`. Prior to v1.1.0, if both were left undefined, the desired revision was determined by Freight (if possible). Beginning with v1.1.0, if both are left undefined, Kargo will not require the source to be observably synced to any particular source to be considered healthy. Note that the source's `targetRevision` will not be updated to this commit unless `updateTargetRevision=true` is also set.<br/><br/>__Deprecated: Use `desiredRevision` with an expression instead. Will be removed in v1.3.0.__ |
+| `apps[].sources[].updateTargetRevision` | `boolean` | Y | Indicates whether the target `ApplicationSource` should be updated such that its `targetRevision` field points directly at the desired revision. A `true` value in this field requires exactly one of `desiredCommitFromStep` or `desiredRevision` to be specified. |
+| `apps[].sources[].kustomize` | `object` | N | Describes updates to an Argo CD `ApplicationSource`'s Kustomize-specific properties. |
+| `apps[].sources[].kustomize.images` | `[]object` | Y | Describes how to update an Argo CD `ApplicationSource`'s Kustomize-specific properties to reference specific versions of container images. |
+| `apps[].sources[].kustomize.images[].repoURL` | `string` | Y | URL of the image being updated. |
+| `apps[].sources[].kustomize.images[].tag` | `string` | N | A tag naming a specific revision of the image specified by `repoURL`. Mutually exclusive with `digest` and `useDigest=true`. One of `digest`, `tag`, or `useDigest=true` must be specified. |
+| `apps[].sources[].kustomize.images[].digest` | `string` | N | A digest naming a specific revision of the image specified by `repoURL`. Mutually exclusive with `tag` and `useDigest=true`. One of `digest`, `tag`, or `useDigest=true` must be specified. |
+| `apps[].sources[].kustomize.images[].useDigest` | `boolean` | N | Whether to use the container image's digest instead of its tag. |
+| `apps[].sources[].kustomize.images[].newName` | `string` | N | A substitution for the name/URL of the image being updated. This is useful when different Stages have access to different container image repositories (assuming those different repositories contain equivalent images that are tagged identically). This may be a frequent consideration for users of Amazon's Elastic Container Registry. |
+| `apps[].sources[].kustomize.images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). If not specified, may inherit a value from `apps[].sources[].kustomize.fromOrigin`. <br/><br/>__Deprecated: Use `digest` or `tag` with an expression instead. Will be removed in v1.3.0.__ |
+| `apps[].sources[].kustomize.fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). If not specified, may inherit a value from `apps[].sources[].fromOrigin`. <br/><br/>__Deprecated: Will be removed in v1.3.0.__ |
+| `apps[].sources[].helm` | `object` | N | Describes updates to an Argo CD `ApplicationSource`'s Helm parameters. |
+| `apps[].sources[].helm.images` | `[]object` | Y | Describes how to update  an Argo CD `ApplicationSource`'s Helm parameters to reference specific versions of container images. |
+| `apps[].sources[].helm.images[].repoURL` | `string` | N | URL of the image being updated. __Deprecated: Use `value` with an expression instead. Will be removed in v1.3.0.__ |
+| `apps[].sources[].helm.images[].key` | `string` | Y | The key to update within the target `ApplicationSource`'s `helm.parameters` map. See Helm documentation on the [format and limitations](https://helm.sh/docs/intro/using_helm/#the-format-and-limitations-of---set) of the notation used in this field. |
+| `apps[].sources[].helm.images[].value` | `string` | Y | Specifies how the value of `key` is to be updated. When `repoURL` is non-empty, possible values for this field are limited to:<ul><li>`ImageAndTag`: Replaces the value of `key` with a string in form `<image url>:<tag>`</li><li>`Tag`: Replaces the value of `key` with the image's tag</li><li>`ImageAndDigest`: Replaces the value of `key` with a string in form `<image url>@<digest>`</li><li>`Digest`: Replaces the value of `key` with the image's digest</li></ul> When `repoURL` is empty, use an expression in this field to describe the new value.  |
+| `apps[].sources[].helm.images[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). If not specified, may inherit a value from `apps[].sources[].helm.fromOrigin`. <br/><br/>__Deprecated: Use `value` with an expression instead. Will be removed in v1.3.0.__ |
+| `apps[].sources[].helm.fromOrigin` | `object` | N | See [specifying origins].(#specifying-origins). If not specified, may inherit a value from `apps[].sources[]`. <br/><br/>__Deprecated: Will be removed in v1.3.0.__ |
+| `apps[].sources[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). If not specified, may inherit a value from `apps[].fromOrigin`. <br/><br/>__Deprecated: Will be removed in v1.3.0.__ |
+| `apps[].fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). If not specified, may inherit a value from `fromOrigin`.  <br/><br/>__Deprecated: Will be removed in v1.3.0.__ |
+| `fromOrigin` | `object` | N | See [specifying origins](#specifying-origins). <br/><br/>__Deprecated: Will be removed in v1.3.0.__  |
+
+## Health Checks
+
+The `argocd-update` step is unique among all other built-in promotion steps in
+that, on successful completion, it will register health checks to be performed
+upon the target Stage on an ongoing basis. This health check configuration is
+_opaque_ to the rest of Kargo and is understood only by health check
+functionality built into the step. This permits Kargo to factor the health and
+sync state of Argo CD `Application` resources into the overall health of a Stage
+without requiring Kargo to understand `Application` health directly.
+
+:::info
+Although the `argocd-update` step is the only promotion step to currently
+utilize this health check framework, we anticipate that future built-in and
+third-party promotion steps will take advantage of it as well.
+:::
+
+## Examples
+
+### Common Usage
+
+```yaml
+steps:
+# Clone, render manifests, commit, push, etc...
+- uses: git-commit
+  as: commit
+  config:
+    path: ./out
+    messageFromSteps:
+    - update-image
+- uses: git-push
+  config:
+    path: ./out
+- uses: argocd-update
+  config:
+    apps:
+    - name: my-app
+      sources:
+      - repoURL: https://github.com/example/repo.git
+        desiredRevision: ${{ outputs.commit.commit }}
+```
+
+### Updating a Target Revision
+
+:::caution
+Without making any modifications to a Git repository, this example simply
+updates a "live" Argo CD `Application` resource to point its `targetRevision`
+field at a specific version of a Helm chart, which Argo CD will pull directly
+from the chart repository.
+
+While this can be a useful technique, it should be used with caution. This is
+not "real GitOps" since the state of the `Application` resource is not backed
+up in a Git repository. If the `Application` resource were deleted, there would
+be no remaining record of its desired state.
+:::
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: argocd-update
+  config:
+    apps:
+    - name: my-app
+      sources:
+      - repoURL: ${{ chartRepo }}
+        chart: my-chart
+        targetRevision: ${{ chartFrom(chartRepo, "my-chart").Version }}
+```
+
+### Updating an Image with Kustomize
+
+:::caution
+Without making any modifications to a Git repository, this example simply
+updates Kustomize-specific properties of a "live" Argo CD `Application`
+resource.
+
+While this can be a useful technique, it should be used with caution. This is
+not "real GitOps" since the state of the `Application` resource is not backed up
+in a Git repository. If the `Application` resource were deleted, there would be
+no remaining record of its desired state.
+:::
+
+```yaml
+vars:
+- name: gitRepo
+  value: https://github.com/example/repo.git
+steps:
+- uses: argocd-update
+  config:
+    apps:
+    - name: my-app
+      sources:
+      - repoURL: https://github.com/example/repo.git
+        kustomize:
+          images:
+          - repoURL: ${{ vars.imageRepo }}
+            tag: ${{ imageFrom(vars.imageRepo).Tag }}
+```
+
+### Updating an Image with Helm
+
+:::caution
+Without making any modifications to a Git repository, this example simply
+updates Helm-specific properties of a "live" Argo CD `Application` resource.
+
+While this can be a useful technique, it should be used with caution. This is
+not "real GitOps" since the state of the `Application` resource is not backed
+up in a Git repository. If the `Application` resource were deleted, there would
+be no remaining record of its desired state.
+:::
+
+```yaml
+steps:
+- uses: argocd-update
+  config:
+    apps:
+    - name: my-app
+      sources:
+      - repoURL: https://github.com/example/repo.git
+        helm:
+          images:
+          - key: image.tag
+            value: ${{ imageFrom("my/image").Tag }}
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/60-http.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/60-http.md
new file mode 100644
index 000000000..d5e0bef05
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/60-http.md
@@ -0,0 +1,188 @@
+---
+sidebar_label: http
+description: Makes an HTTP/S request to enable basic integration with a wide variety of external services.
+---
+
+# `http`
+
+`http` is a generic step that makes an HTTP/S request to enable basic integration
+with a wide variety of external services.
+
+## Configuration
+
+| Name | Type | Required | Description |
+|------|------|----------|-------------|
+| `method` | `string` | Y | The HTTP method to use. |
+| `url` | `string` | Y | The URL to which the request should be made. |
+| `headers` | `[]object` | N | A list of headers to include in the request. |
+| `headers[].name` | `string` | Y | The name of the header. |
+| `headers[].value` | `string` | Y | The value of the header. |
+| `queryParams` | `[]object` | N | A list of query parameters to include in the request. |
+| `queryParams[].name` | `string` | Y | The name of the query parameter. |
+| `queryParams[].value` | `string` | Y | The value of the query parameter. The provided value will automatically be URL-encoded if necessary. |
+| `body` | `string` | N | The body of the request. __Note:__ As this field is a `string`, take care to utilize [`quote()`](../20-expressions.md#quote) if the body is a valid JSON `object`. Refer to the example below of posting a message to a Slack channel. |
+| `insecureSkipTLSVerify` | `boolean` | N | Indicates whether to bypass TLS certificate verification when making the request. Setting this to `true` is highly discouraged. |
+| `timeout` | `string` | N | A string representation of the maximum time interval to wait for a request to complete. _This is the timeout for an individual HTTP request. If a request is retried, each attempt is independently subject to this timeout._ See Go's [`time` package docs](https://pkg.go.dev/time#ParseDuration) for a description of the accepted format. |
+| `successExpression` | `string` | N | An [expr-lang] expression that can evaluate the response to determine success. If this is left undefined and `failureExpression` _is_ defined, the default success criteria will be the inverse of the specified failure criteria. If both are left undefined, success is `true` when the HTTP status code is `2xx`. If `successExpression` and `failureExpression` are both defined and both evaluate to `true`, the failure takes precedence. Note that this expression should _not_ be offset by `${{` and `}}`. See examples for more details. |
+| `failureExpression` | `string` | N | An [expr-lang] expression that can evaluate the response to determine failure. If this is left undefined and `successExpression` _is_ defined, the default failure criteria will be the inverse of the specified success criteria. If both are left undefined, failure is `true` when the HTTP status code is _not_ `2xx`. If `successExpression` and `failureExpression` are both defined and both evaluate to `true`, the failure takes precedence. Note that this expression should _not_ be offset by `${{` and `}}`. See examples for more details. |
+| `outputs` | `[]object` | N | A list of rules for extracting outputs from the HTTP response. These are only applied to responses deemed successful. |
+| `outputs[].name` | `string` | Y | The name of the output. |
+| `outputs[].fromExpression` | `string` | Y | An [expr-lang] expression that can extract a value from the HTTP response. Note that this expression should _not_ be offset by `${{` and `}}`. See examples for more details. |
+
+:::note
+An HTTP response that is not conclusively determined to have succeeded or failed
+will result in the step reporting a result of `Running`. Kargo will
+[retry](../15-promotion-templates.md#step-retries) such a step on its next
+attempt at reconciling the`Promotion` resource. This will continue until the step
+succeeds, fails, exhausts the configured maximum number of retries, or a configured
+timeout has elapsed.
+:::
+
+## Expressions
+
+The `successExpression`, `failureExpression`, and `outputs[].fromExpression`
+fields all support [expr-lang] expressions.
+
+:::note
+The expressions included in the `successExpression`, `failureExpression`, and
+`outputs[].fromExpression` fields should _not_ be offset by `${{` and `}}`. This
+is to prevent the expressions from being evaluated by Kargo during
+pre-processing of step configurations. The `http` step itself will evaluate
+these expressions.
+:::
+
+A `response` object (a `map[string]any`) is available to these expressions. It
+is structured as follows:
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `status` | `int` | The HTTP status code of the response. |
+| `headers` | `http.Header` | The headers of the response. See applicable [Go documentation](https://pkg.go.dev/net/http#Header). |
+| `header` | `func(string) string` | `headers` can be inconvenient to work with directly. This function allows you to access a header by name. |
+| `body` | `map[string]any` | The body of the response, if any, unmarshaled into a map. If the response body is empty, this map will also be empty. |
+
+## Outputs
+
+The `http` step only produces the outputs described by the `outputs` field of
+its configuration.
+
+## Examples
+
+### Basic Usage
+
+This examples configuration makes a `GET` request to the
+[Cat Facts API.](https://www.catfacts.net/api/) and uses the default
+success/failure criteria.
+
+```yaml
+steps:
+# ...
+- uses: http
+  as: cat-facts
+  config:
+    method: GET
+    url: https://www.catfacts.net/api/
+    outputs:
+    - name: status
+      fromExpression: response.status
+    - name: fact1
+      fromExpression: response.body.facts[0]
+    - name: fact2
+      fromExpression: response.body.facts[1]
+```
+
+Assuming a `200` response with the following JSON body:
+
+```json
+{
+    "facts": [
+        {
+            "fact_number": 1,
+            "fact": "Kittens have baby teeth, which are replaced by permanent teeth around the age of 7 months."
+        },
+        {
+            "fact_number": 2,
+            "fact": "Each day in the US, animal shelters are forced to destroy 30,000 dogs and cats."
+        }
+    ]
+}
+```
+
+The step would succeed and produce the following outputs:
+
+| Name     | Type | Value |
+|----------|------|-------|
+| `status` | `int` | `200` |
+| `fact1` | `string` | `Kittens have baby teeth, which are replaced by permanent teeth around the age of 7 months.` |
+| `fact2` | `string` | `Each day in the US, animal shelters are forced to destroy 30,000 dogs and cats.` |
+
+### Polling
+
+Building on the basic example, this configuration defines explicit success and
+failure criteria. Any response meeting neither of these criteria will result in
+the step reporting a result of `Running` and being retried. Note the use of
+[retry](../15-promotion-templates.md#step-retries) configuration to set a timeout
+for the step.
+
+```yaml
+steps:
+# ...
+- uses: http
+  as: cat-facts
+  retry:
+    timeout: 10m
+  config:
+    method: GET
+    url: https://www.catfacts.net/api/
+    successExpression: response.status == 200
+    failureExpression: response.status == 404
+    outputs:
+    - name: status
+      fromExpression: response.status
+    - name: fact1
+      fromExpression: response.body.facts[0]
+    - name: fact2
+      fromExpression: response.body.facts[1]
+```
+
+Our request is considered:
+
+- Successful if the response status is `200`.
+- A failure if the response status is `404`.
+- Running if the response status is anything else. i.e. Any other status code
+  will result in a retry.
+
+### Posting to Slack
+
+This examples is adapted from
+[Slack's own documentation](https://api.slack.com/tutorials/tracks/posting-messages-with-curl):
+
+```yaml
+vars:
+- name: slackChannel
+  value: C123456
+steps:
+# ...
+- uses: http
+  config:
+    method: POST
+    url: https://slack.com/api/chat.postMessage
+    headers:
+    - name: Authorization
+      value: Bearer ${{ secrets.slack.token }}
+    - name: Content-Type
+      value: application/json
+    body: |
+      ${{ quote({
+        "channel": vars.slackChannel,
+        "blocks": [
+          {
+            "type": "section",
+            "text": {
+              "type": "mrkdwn",
+              "text": "Hi I am a bot that can post *_fancy_* messages to any public channel."
+            }
+          }
+        ]
+      }) }}
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/70-compose-output.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/70-compose-output.md
new file mode 100644
index 000000000..0a9a6595f
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/70-compose-output.md
@@ -0,0 +1,65 @@
+---
+sidebar_label: compose-output
+description: Composes output from one or more steps into new output.
+---
+
+# `compose-output`
+
+`compose-output` is a step that composes a new output from one or more existing
+outputs. This step can be useful when subsequent steps need to reference a
+combination of outputs from previous steps, or to allow a
+[`PromotionTask`](../35-promotion-tasks.md) to provide easy access to outputs from
+the steps it contains.
+
+## Configuration
+
+The `compose-output` step accepts an arbitrary set of key-value pairs, where the
+key is the name of the output to be created and the value is arbitrary and can
+be an [Expression Language](../20-expressions.md) expression.
+
+## Output
+
+The dynamic outputs of the `compose-output` step are the outputs that it composes
+according to its configuration.
+
+## Examples
+
+```yaml
+vars:
+- name: repoURL
+  value: https://github.com/example/repo
+steps:
+- uses: git-open-pr
+  as: open-pr
+  config:
+    repoURL: ${{ vars.repoURL }}
+    createTargetBranch: true
+    sourceBranch: ${{ outputs.push.branch }}
+    targetBranch: stage/${{ ctx.stage }}
+- uses: compose-output
+  as: pr-link
+  config:
+    url: ${{ vars.repoURL }}/pull/${{ outputs['open-pr'].prNumber }}
+- uses: http
+  config:
+    method: POST
+    url: https://slack.com/api/chat.postMessage
+    headers:
+    - name: Authorization
+      value: Bearer ${{ secrets.slack.token }}
+    - name: Content-Type
+      value: application/json
+    body: |
+      ${{ quote({
+        "channel": "C123456",
+        "blocks": [
+          {
+            "type": "section",
+            "text": {
+              "type": "mrkdwn",
+              "text": "A new PR has been opened: ${{ outputs['pr-link'].url }}"
+            }
+          }
+        ]
+      }) }}
+```
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/_category_.json b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/_category_.json
new file mode 100644
index 000000000..ec893887d
--- /dev/null
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps/_category_.json
@@ -0,0 +1,11 @@
+{
+  "label": "Promotion Steps",
+  "link": {
+    "type": "generated-index",
+    "title": "Promotion Steps Reference",
+    "description": "Reference documentation for the steps available to use in promotions.",
+    "slug": "new-docs/user-guide/reference-docs/promotion-steps"
+  },
+  "collapsible": true,
+  "collapsed": true
+}
diff --git a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps.md b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/35-promotion-tasks.md
similarity index 54%
rename from docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps.md
rename to docs/docs/60-new-docs/60-user-guide/60-reference-docs/35-promotion-tasks.md
index 5c4977395..77f983423 100644
--- a/docs/docs/60-new-docs/60-user-guide/60-reference-docs/30-promotion-steps.md
+++ b/docs/docs/60-new-docs/60-user-guide/60-reference-docs/35-promotion-tasks.md
@@ -1,8 +1,8 @@
 ---
-sidebar_label: Expression Language
+sidebar_label: Promotion Tasks
 ---
 
-# Promotion Steps Reference
+# Promotion Tasks Reference
 
 :::warning
 Hidde to put something here by the end of the week
diff --git a/docs/docs/60-new-docs/80-release-notes/97-v1.2.0.md b/docs/docs/60-new-docs/80-release-notes/97-v1.2.0.md
new file mode 100644
index 000000000..053d0d046
--- /dev/null
+++ b/docs/docs/60-new-docs/80-release-notes/97-v1.2.0.md
@@ -0,0 +1,141 @@
+## 🆕 What's New?
+
+### 💪 Promotion Tasks
+
+When support for expressions in promotion steps debuted in Kargo v1.1.0, we had a vision of eventually leveraging that capability to define reusable sequences of steps, where the particulars of each `Stage` utilizing them in their `Promotion`s could be provided, essentially, as arguments. v1.2.0 makes that vision a reality with the introduction of `PromotionTask`s (and `ClusterPromotionTask`s).
+
+We've observed the majority of our users housing their application configurations in monorepos, so with little difficulty, we can imagine such a repository housing configuration for dozens or even hundreds of applications, with each of those configurations also having a number of variations for each of several environments. Among these applications, many are likely to employ the same directory structure and configuration management tools. Prior to Kargo v1.2.0, each and every `Stage` representing an application/environment pair would have had to individually define a promotion process that would have been remarkably similar from one to the next.
+
+With `PromotionTask`s, a sequence of common steps can be defined like so:
+
+```yaml
+apiVersion: kargo.akuity.io/v1alpha1
+kind: PromotionTask
+metadata:
+name: standard-process
+namespace: guestbook
+spec:
+vars:
+- name: app
+- name: imageRepo
+steps:
+- uses: git-clone
+config:
+repoURL: https://github.com/example/monorepo.git
+checkout:
+- path: ./configs
+- uses: yaml-update
+config:
+path: ./configs/${{ vars.app }}/chart/envs/${{ ctx.stage }}/values.yaml
+updates:
+- key: image.tag
+value: ${{ imageFrom(vars.imageRepo).Tag }}
+- uses: git-commit
+config:
+path: ./configs
+- uses: git-push
+config:
+path: ./configs
+- uses: argocd-update
+config:
+apps:
+- name: ${{ vars.app }}-${{ ctx.stage }}
+```
+
+This `PromotionTask` can then be referenced by any number of `Stage`s within the same project:
+
+```yaml
+apiVersion: kargo.akuity.io/v1alpha1
+kind: Stage
+metadata:
+name: uat
+namespace: guestbook
+spec:
+requestedFreight:
+- origin:
+kind: Warehouse
+name: guestbook
+sources:
+stages:
+- test
+promotionTemplate:
+spec:
+vars:
+- name: app
+value: guestbook
+- name: imageRepo
+value: company/guestbook
+steps:
+- task:
+name: standard-process
+```
+
+To use a common sequence of steps across multiple projects, use a cluster-scoped `ClusterPromotionTask` resource instead.
+
+To learn more about this exciting feature, refer to our [PromotionTasks reference doc](https://docs.kargo.io/references/promotion-steps).
+
+### 🌊 Soak Time
+
+A _frequent_ request from users has been to support an option whereby a `Stage` may require any `Freight` promoted to it to have first "soaked" (remained in) an upstream `Stage` for a certain period of time, and this is now possible in v1.2.0.
+
+```yaml
+apiVersion: kargo.akuity.io/v1alpha1
+kind: Stage
+metadata:
+name: uat
+namespace: guestbook
+spec:
+requestedFreight:
+- origin:
+kind: Warehouse
+name: guestbook
+sources:
+stages:
+- test
+requiredSoakTime: 1h
+promotionTemplate:
+# Omitted for brevity...
+```
+
+Note that `requiredSoakTime`, if specified, is _in addition to_ the usual criteria that `Freight` must have been verified upstream before becoming available for promotion.
+
+### 🪜 New and Updated Promotion Steps
+
+* A new `json-update` allows for performing updates to JSON files in the same manner that has been possible for YAML files using the `yaml-update` step.
+
+* A new `delete` promotion step can be used to delete files or directories.
+
+* Thanks to the diligent efforts of @diegocaspi, the `git-open-pr` and `git-wait-for-pr` promotion steps now support Azure DevOps repositories.
+
+* @muenchdo generously contributed two new options for the `git-open-pr` promotion step to specify a user-defined title and user-defined labels for the PRs it opens.
+
+Refer to the [Promotion Steps reference doc](https://docs.kargo.io/references/promotion-steps) for more details.
+
+### 🖥️ UI Improvements
+
+The two most notable UI improvements in v1.2.0 are:
+
+* When viewing a `Stage`s verification history, it is now possible to filter out "implicit" verification records that are created when a `Stage` lacking any user-defined verification process simply becomes healthy with any new `Freight` that has been promoted to it.
+
+* Project-scoped Kubernetes `Secret`s can now be managed in the UI.
+
+### ⚙️ Chart Improvements
+
+We've, several times now, encountered users who are terminating TLS somewhere "upstream" from the Kargo API server (for instance at a reverse proxy or load balance). This has tended to impose some difficulty as the API server, itself not being configured to terminate TLS, would be unaware that any URLs it generates should begin with `https://` regardless.
+
+To address this, we've introduced a new `api.tls.terminatedUpstream` that can be set to `true` at install time.
+
+For further information, please refer directly to the [Kargo Helm chart's README](https://github.com/akuity/kargo/blob/main/charts/kargo/README.md), which describes all configuration options in detail.
+
+## 🙏 New Contributors
+
+Kargo would be nothing without its users. An extra special thank you goes out to community members who made their first contribution to Kargo in this release:
+
+* @diegocaspi
+* @ggogel
+* @magisystem0408
+* @RohanMishra315
+* @Sebast1aan
+* @Sobuno
+
+**Full Changelog**: https://github.com/akuity/kargo/compare/v1.1.2...v1.2.0
diff --git a/docs/docs/60-new-docs/80-release-notes/98-v1.1.0.md b/docs/docs/60-new-docs/80-release-notes/98-v1.1.0.md
new file mode 100644
index 000000000..131059aea
--- /dev/null
+++ b/docs/docs/60-new-docs/80-release-notes/98-v1.1.0.md
@@ -0,0 +1,78 @@
+💪 The Kargo team, with support from community contributors, is proud to present v1.1.0 -- Kargo's first minor but _mighty_ release since going GA.
+
+## 🆕 What's New?
+
+### `${{ Expression Language Support }}`
+
+The community reception to Kargo's transition away from its rigid, legacy promotion mechanisms, toward more flexible promotion steps has been overwhelmingly positive. When promotion steps first appeared in v0.9.0, we knew immediately that support for an expression language would be a powerful complement to that new feature, and this release delivers on that.
+
+For more details, consult our [expression reference documentation](https://docs.kargo.io/references/expression-language). Examples in our [promotion steps reference documentation](https://docs.kargo.io/references/promotion-steps) have also been extensively updated to reflect realistic usage of the expression language.
+
+With the advent of expression language support in promotion steps, we're noticing our own promotions processes becoming more and more similar from Stage to Stage -- often varying only in the definition of a few key variables. With this observation, it's clear that the time is right to _promote_ (pun fully intended) promotion processes to a first-class construct. So, to tease the upcoming v1.2.0 release, expect to see a new `PromotionTemplate` CRD that will enable users to DRY up their pipelines!
+
+### 🪜 New and Updated Promotion Steps
+
+‼️ __This section contains important details about deprecations.__ ‼️
+
+When promotion steps were initially introduced _without_ expression language support, many promotion steps included fields explicitly designed to reference the output of previous steps. For example, the `git-wait-for-pr` step has a `prNumberFromStep` field whose value should be set to the alias of a previous `git-open-pr` step. With expressions, these sort of highly-specialized fields become unnecessary and have been deprecated and scheduled for removal in v1.3.0. In their place, are new fields that, combined with expressions, offer improved flexibility. The aforementioned `git-wait-for-pr` step, for example, now has a `prNumber` field whose value might be set using an expression such as `${{ outputs['open-pr'].prNumber }}`.
+
+Two _new_ promotion steps have been added in this release:
+
+* `yaml-update`, with the help of expressions, presents a more generic and flexible alternative to the `helm-update-image` step, which is also now deprecated and scheduled for removal in v1.3.0.
+
+* `http` provides a flexible means of interacting with HTTP/S endpoints. This opens up the possibility of simple, low-level integration with external systems that support webhooks or expose RESTful APIs. It is easy, for instance, to use the `http` step to post a message to a Slack channel as part of a promotion process.
+
+While we plan for more complex integrations with external systems to be phased in over a series of releases in the form of support for third-party or site-specific promotion steps, we believe that in the interim, the `http` step will provide a powerful and flexible means of integrating with a wide variety of systems.
+
+Two steps have been updated:
+
+* `argo-cd-update` has undergone some behavioral changes. Until now, the step, which registers _health checks_ to be performed in the course of Stage reconciliation, has automatically attempted to infer a specific desired revision (e.g. a Git commit SHA) to which the `Application` being updated should be observably synced to in order for the Stage to be considered healthy. This behavior has been the foundation of some difficulty for users who have multiple `Application`s tracking the head of a single branch and who update that branch from multiple Stages. Under such circumstances, it becomes impossible for all such Stages to be healthy simultaneously.
+
+To correct for this, the `argo-cd-update` step now makes no attempt to automatically infer the desired revision and will only factor the revision to which an `Application` is synced into a health check when the desired revision has been explicitly specified in the step's configuration. This change in behavior is technically a breaking change, but as it relaxes a constraint rather than imposing a new one, we do not anticipate any significant impact to existing uses of the step.
+
+* `git-open-pr` has been prone to errors when a PR identical to the one it attempts to create already exists. There were a variety of complex conditions that may have precipitated such a scenario. The step (and parts of the step execution engine) have been refactored to make the step more resilient to this possibility. When a PR identical to the one the step intends to create already exists, the step will now simply "adopt" that PR and proceed as if successful.
+
+Last, but not least, all steps can now be configured with an optional timeout and error threshold. An error threshold greater than the default of one specifies the number of consecutive failed attempts to execute the step must occur before the entire Promotion is failed.
+
+__Please refer to the [promotion steps reference documentation](https://docs.kargo.io/references/promotion-steps) for detailed information about new and updated promotion steps as well as deprecated steps and fields.__
+
+### ⚙️ Resource and Concurrency Settings
+
+This release introduces a number of optimizations to Kargo's resource utilization.
+
+* [`GOMAXPROCS`](https://pkg.go.dev/runtime#GOMAXPROCS) is now set on all Kargo components to equal the CPU cores available, rounded up to the nearest integer. This prevents Go from backing goroutines with a number of OS threads _exceeding_ the number of cores available, which is a condition that can result in losing compute time to avoidable context switches.
+
+* [`GOMEMLIMIT`](https://pkg.go.dev/runtime#hdr-Environment_Variables) (soft memory limit) is now set on all Kargo components to equal the container's memory limit. This helps Go to optimize garbage collection.
+
+* [`MaxConcurrentReconciles`](https://pkg.go.dev/github.com/kubernetes-sigs/controller-runtime/pkg/controller#Options) now defaults to _four_ (instead of one) for all reconcilers in both the controller and management controller. These defaults are overridable on a per-controller or per-reconciler basis via chart configuration at install-time.
+
+To assist in troubleshooting, effective values for all of the above are logged by each component at startup.
+
+### 🛠️ Refactored Stage Reconciliation
+
+The controller's Stage reconciliation logic has been overhauled from top to bottom. The new implementation is more robust, more efficient, and should prove easier to maintain over time. We anticipate the refactored reconciler to also reduce the incidence of inconvenient behaviors such as pending Promotions being "stuck" for long periods of time while waiting for a Stage to reach or return to a healthy state that it may never reach.
+
+For the most part, these changes are purely internal, but users may notice that the reconciler now surfaces much more detailed information about the state of a Stage in its `status` subresource in the form of _conditions_. These should paint a clearer picture of what's happening with a Stage at any given time.
+
+### 🖥️ UI Improvements
+
+As always, the Kargo UI has received too many improvements to list in this release. Here are a few highlights:
+
+* New Warehouses can now be interactively configured using a new UI wizard.
+
+* The UI's homepage, which lists all Kargo Projects, now remembers what page of the paginated list the user was last viewing. This means that users returning to the homepage after navigating to a Project will be returned to the same page of the list they were viewing before.
+
+* Within the view of a single Project, it is now possible to zoom in/out on the pipeline graph and drag to reposition it. This should make it considerably easier to work with long pipelines or Projects containing many pipelines.
+
+* A new user information page accessible from the sidebar displays the currently logged-in user's claims obtained from the identity token issued by the configured' OIDC identity provider. We anticipate this page will be useful for debugging OIDC configuration issues as well as authorization issues.
+
+## 🙏 New Contributors
+
+Thank you to the following community members whose first contributions to Kargo were included in this release:
+
+* @ntheanh201
+* @ddeath
+* @sergiofteixeira
+* @Historyman
+
+**Full Changelog**: https://github.com/akuity/kargo/compare/v1.0.4...v1.1.0
\ No newline at end of file
diff --git a/docs/docs/60-new-docs/80-release-notes/99-v1.0.0.md b/docs/docs/60-new-docs/80-release-notes/99-v1.0.0.md
new file mode 100644
index 000000000..21bd2d2d4
--- /dev/null
+++ b/docs/docs/60-new-docs/80-release-notes/99-v1.0.0.md
@@ -0,0 +1,59 @@
+💥 Kargo v1.0.0 (GA) is finally here!
+
+## 🆕 What's New?
+
+Not a lot. (Which is what you want in the GA release!) The main focus of v1.0.0 has been on stability and completing the pivot from rigid promotion mechanisms to flexible promotion steps that started with the v0.9.0 release.
+
+Here's a short list of noteworthy new features and fixes:
+
+### 🆕 General Improvements
+
+- Warehouses more consistently discover new Freight at the proper interval.
+
+- Promotions no longer pre-empt running or pending verification processes.
+
+### 🪜 Promotion Step Improvements
+
+- Promotion steps will fail when obvious misconfigurations are detected.
+
+- Git-based promotion steps now support SSH authentication. (Warehouses already supported this.)
+
+- The `kustomize-build` promotion step now supports Helm chart inflation.
+
+### 🖥️ UI Improvements
+
+- The detailed Stage view now includes a timeline of the Stage's Freight history.
+
+- Running and pending Promotions can now be aborted directly from the UI.
+
+- Promotion workflows can be composed in the UI without writing YAML.
+
+### 🛡️ Security Improvements
+
+- The official Kargo container image is now distroless. With a much smaller footprint overall, Kargo's attackable surface is reduced and maintainers will be able to more quickly respond to critical CVEs.
+
+- Kargo controllers (which may run on clusters other than the Kargo control plane) no longer require cluster-wide read access to Secrets. Instead, the management controller (a control plane component) will dynamically expand and contract the scope of all other controllers' Secret access as Projects are created and deleted. (The management controller has already done this same thing for the API server for quite some time.)
+
+## ‼️ Breaking Changes
+
+If you have designated any namespaces as "global" credential stores by providing values to `controller.globalCredentials.namespaces` at install-time, please note that you will need to either:
+
+1. Provide your own `RoleBinding`s to permit the Kargo controller(s) to read `Secret`s from each of those namespaces
+
+OR
+
+2. ⚠️ Highly discouraged: Set `controller.serviceAccount.clusterWideSecretReadingEnabled` to `true`
+
+Apart from this and the final removal of the legacy promotion mechanisms, which were deprecated in v0.9.0, there are no breaking changes in this release.
+
+If you still rely on the legacy promotion mechanisms, we plan to continue releasing v0.9.x patches through the end of the year to ensure users have ample time to complete the migration.
+
+## 🙏 New Contributors
+
+Thank you to the following community members whose first contributions to Kargo were included in this release:
+
+* @cuishuang
+* @kpanter
+* @muenchdo
+
+__Full Changelog:__ https://github.com/akuity/kargo/compare/v0.9.1...v1.0.0
diff --git a/docs/docs/60-new-docs/80-release-notes/_category_.json b/docs/docs/60-new-docs/80-release-notes/_category_.json
new file mode 100644
index 000000000..e264f3479
--- /dev/null
+++ b/docs/docs/60-new-docs/80-release-notes/_category_.json
@@ -0,0 +1,5 @@
+{
+  "label": "Release Notes",
+  "collapsible": true,
+  "collapsed": true
+}
diff --git a/docs/docs/60-new-docs/80-release-notes/index.md b/docs/docs/60-new-docs/80-release-notes/index.md
deleted file mode 100644
index fa595b3ef..000000000
--- a/docs/docs/60-new-docs/80-release-notes/index.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-sidebar_label: Release Notes
----
-
-# Release Notes
-
-:::warning
-Faeka to add a page per minor version >= 1.0 to this section by the end of the
-week. These already exist and just need to be formatted for presentation here.
-:::