Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ flowchart TD

Most pipelines can only hotfix the tip, which in practice means production. cascade hotfixes **any** environment:

- It stages the fix on a per-environment integration branch.
- It deploys that one environment with a clean `-rc.N.hotfix.M` version.
- It rejoins trunk the next time a trunk SHA that already contains the fix is promoted.
- It stages the fix or set of fixes on a per-environment integration branch.
- A hotfix can carry a set of commits and elevate them across the chain up to the target environment.
- It deploys the diverged environments with a clean `-rc.N.hotfix.M` version.
- It rejoins trunk the next time a trunk SHA that already contains the fixes is promoted.

The example below lands a fix on **staging** while dev, test, and prod stay exactly where they are.

Expand Down
25 changes: 20 additions & 5 deletions docs/src/content/docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,11 +583,16 @@ cascade promote finalize \

### hotfix

Apply a trunk commit onto an environment pinned to an older base. A hotfix targets one environment on its `env/<env>` integration branch. The fix must already be on trunk; cascade refuses to apply a commit that is not an ancestor of trunk tip. The subcommands compute and validate the hotfix and write its final state; the cherry-pick, build, and deploy run in the generated `cascade-hotfix.yaml` workflow. See the Hotfix section of [Workflows](/cascade/workflows/) for the full flow.
Apply one or more trunk commits onto an environment pinned to an older base. A hotfix elevates the commit set bottom-up across the environment chain, up to and including the target environment, on each environment's `env/<env>` integration branch. The fixes must already be on trunk; cascade refuses to apply any commit that is not an ancestor of trunk tip. The subcommands compute and validate the hotfix and write its final state; the cherry-pick, build, and deploy run in the generated `cascade-hotfix.yaml` workflow. See the Hotfix section of [Workflows](/cascade/workflows/) for the full flow.

#### hotfix plan

Validate a hotfix request and compute the integration-branch plan. It enforces, in order: trunk ancestry of the fix, target-environment eligibility (a configured environment that is not the first; prod is allowed), no-op detection when the fix is already in the target, the single-flight open-pull-request gate, and `env/<env>` branch reconciliation. With `--dry-run` nothing is mutated (the env branch is planned but not created).
Validate a hotfix request and compute the integration-branch plan. It enforces, in order: trunk ancestry of every fix, target-environment eligibility (a configured environment that is not the first; prod is allowed), no-op detection when a fix is already present, the single-flight open-pull-request gate, and `env/<env>` branch reconciliation. With `--dry-run` nothing is mutated (the env branches are planned but not created).

Supply the fixes with one of two mutually exclusive flags, exactly one of which is required:

- `--commit <sha>` applies a single commit to the target environment.
- `--commits <sha,sha,...>` takes a comma-delimited set and elevates it bottom-up across the chain, from the environment above the first up to and including `--target-env`. On this path each (commit, environment) pair is skipped when the commit is already an ancestor of that environment's state SHA or already in its recorded `patches`; an environment whose whole set is already present is a no-op and the chain moves on.

```bash
cascade hotfix plan \
Expand All @@ -596,13 +601,23 @@ cascade hotfix plan \
--gha-output
```

To carry a set of commits and elevate them across the chain:

```bash
cascade hotfix plan \
--commits abc1234,def5678 \
--target-env staging \
--gha-output
```

##### Flags

| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--config`, `-c` | string | No | Path to manifest file (default: `.github/manifest.yaml`) |
| `--key` | string | No | Top-level manifest key (default: `ci`) |
| `--commit` | string | Yes | Trunk commit (SHA or ref) carrying the fix |
| `--commit` | string | One of `commit`/`commits` | Single trunk commit (SHA or ref) carrying the fix; single-env path |
| `--commits` | string | One of `commit`/`commits` | Comma-delimited trunk commits to elevate across the env chain up to `--target-env` |
| `--target-env` | string | Yes | Environment to hotfix |
| `--actor` | string | No | Actor recorded on the plan (default: `$GITHUB_ACTOR`) |
| `--remote` | string | No | Git remote env branches live on (default: `origin`) |
Expand Down Expand Up @@ -630,7 +645,7 @@ With `--json`:
}
```

The GHA output writes `target_env`, `fix_sha`, `branch`, `base_sha`, `no_op`, `branch_created`, `hotfix_version_candidate`, `conflict_expected`, `dry_run`, and the `protection_suggestions` commands (as JSON and as multiline text).
The GHA output writes `target_env`, `fix_sha`, `branch`, `base_sha`, `no_op`, `branch_created`, `hotfix_version_candidate`, `conflict_expected`, `dry_run`, and the `protection_suggestions` commands (as JSON and as multiline text). On the `--commits` path the plan also writes `env_sequence` (the environments to walk bottom-up) and a `commits_<env>` list per environment that the apply job replays in order.

#### hotfix finalize

Expand All @@ -654,7 +669,7 @@ cascade hotfix finalize \
| `--key` | string | No | Top-level manifest key (default: `ci`) |
| `--target-env` | string | Yes | Environment to finalize |
| `--merge-sha` | string | Yes | Tip of `env/<target>` after the resolution pull request merged |
| `--fix-sha` | string | Yes | Trunk commit the hotfix carries |
| `--fix-sha` | string | Yes | Trunk commit(s) the hotfix carries; comma-delimited for a multi-commit set. Every commit applied to the environment is appended to its recorded `patches` (commits already present in that environment are skipped) |
| `--base-sha` | string | Yes | Trunk anchor the integration branch diverged from |
| `--actor` | string | No | Actor recorded on the state (default: `$GITHUB_ACTOR`) |
| `--dry-run` | bool | No | Validate and compute without writing state, tags, or releases |
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Older tags outside the current release line do not receive backported fixes. See

## Hotfix version segment

A hotfix applies a single trunk commit onto an environment pinned to an older trunk base (see the Hotfix section of [Workflows](/cascade/workflows/)). The version cascade allocates for a hotfix depends on whether the environment's current version is still in flight (an rc) or already published.
A hotfix applies one or more trunk commits onto an environment pinned to an older trunk base (see the Hotfix section of [Workflows](/cascade/workflows/)). The version cascade allocates for a hotfix depends on whether the environment's current version is still in flight (an rc) or already published.

### rc-based (unpublished) base

Expand Down
14 changes: 10 additions & 4 deletions docs/src/content/docs/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,12 @@ flowchart TD
CP -- "conflict" --> PRconf["resolution PR · <b>cascade-hotfix-conflict</b><br/>markers committed; human force-pushes head"]
PRclean --> MERGE["on merge"]
PRconf --> MERGE
MERGE --> FIN["build -> deploy one env -> finalize<br/>vX.Y.Z-rc.N.hotfix.M · ref env/&lt;env&gt; · patches [fix]"]
MERGE --> FIN["build -> deploy one env -> finalize<br/>vX.Y.Z-rc.N.hotfix.M · ref env/&lt;env&gt; · patches [fixes]"]
FIN --> DIV["environment diverged<br/>other environments untouched"]
DIV == "promote a trunk SHA containing the fix<br/>patch-containment guard refuses dropping it" ==> REJOIN["rejoin trunk<br/>divergence cleared · env/&lt;env&gt; deleted"]
```

A hotfix applies a single trunk commit onto an environment that is pinned to an older trunk base, without dragging in the intervening commits. This is the case the standard promote flow cannot serve: promoting a pointer forward would advance the target environment past every commit between its base and the fix, which is exactly what an operator pinning that environment is trying to avoid.
A hotfix applies one or more trunk commits onto an environment that is pinned to an older trunk base, without dragging in the intervening commits. This is the case the standard promote flow cannot serve: promoting a pointer forward would advance the target environment past every commit between its base and the fix or set of fixes, which is exactly what an operator pinning that environment is trying to avoid.

### Roll forward on trunk first (the default)

Expand All @@ -292,12 +292,18 @@ state:

`cascade status` surfaces `ref`, `base_sha`, and `patches` only when they are set.

### Elevating across the chain

A hotfix can carry a set of commits to a target environment higher in the chain. cascade elevates the set bottom-up across every environment from the one above the first up to and including the target, so each environment that must diverge ends up running its base plus the fixes. Per environment, any commit already present (an ancestor of that environment's state SHA, or already in its `patches`) is skipped; an environment whose whole set is already present is a no-op and the chain moves on. Every commit applied to an environment is recorded in that environment's `patches`, so the recorded set reflects every fix applied there, not just the first. The first environment is never a hotfix target: a fix reaches it by merging to trunk, not by hotfix.

### Cherry-pick and resolution pull request

A clean cherry-pick opens a pull request labeled `cascade-hotfix` and merges it as the configured `state_token`. The apply job polls the pull request until it is mergeable, so the required checks configured on `env/<env>` still gate the merge, and the pull request is the audit record even when no human touches it. The merge runs as `state_token` rather than the default `GITHUB_TOKEN` on purpose: a merge authored by `GITHUB_TOKEN` does not emit the `pull_request` close event, so the build, deploy, and finalize stages would never run and the diverged state would never be recorded. Configure `state_token` with a trigger-capable token (the same one used for state writes) to get the post-merge stages after an automated hotfix.

On conflict, the conflicted tree is committed with its conflict markers intact, the branch is pushed, and the pull request is opened labeled `cascade-hotfix-conflict`. Committing the markers makes the resolution pull request a real, checkout-able branch: the diff shows exactly where the conflict is, and a human resolves it locally by force-pushing the head branch.

On the chain path a conflict halts the elevation: the environments still pending are listed in the resolution pull request body, and the later environments are left untouched. After the resolution merges, re-engage the hotfix workflow targeting the same environment to resume the chain from where it stopped.

```
git fetch && git switch hotfix/<env>/<short-sha>
# resolve conflicts, then
Expand All @@ -324,15 +330,15 @@ The divergence ends the next time the environment receives a normal promotion. P

The workflow carries two triggers in one file:

- `workflow_dispatch` with inputs `commit` (the trunk fix SHA), `target_env` (a choice over every configured environment except the first), `pr_number` (optional, to replay an existing resolution pull request), and `dry_run`.
- `workflow_dispatch` with inputs `commit` (one or more trunk fix SHAs, comma-delimited), `target_env` (a choice over every configured environment except the first), `pr_number` (optional, to replay an existing resolution pull request), and `dry_run`.
- `pull_request` on `types: [closed]` against `branches: ['env/*']`, with the post-merge stages gated on the pull request having merged and carrying the `cascade-hotfix` label.

Its jobs:

| Job | Trigger | Role |
| --- | --- | --- |
| plan | dispatch | Fetch env branches and tags, run `cascade hotfix plan`, surface branch-protection suggestions as `::notice::` lines |
| apply | dispatch (not dry-run) | Cherry-pick onto `hotfix/<env>/<sha>`, open the resolution pull request (clean polls until mergeable then merges as `state_token`; conflict opens the labeled resolution pull request) |
| apply | dispatch (not dry-run) | Cherry-pick the set onto each environment bottom-up; clean picks open the resolution pull request (polled until mergeable, then merged as `state_token`), a conflict opens the labeled resolution pull request and halts the chain |
| check | open pull request to `env/*` | Validate the manifest while the hotfix pull request is open |
| build | merged hotfix | Build the merge SHA, since a cherry-picked commit has no prebuilt artifact |
| deploy | merged hotfix | Deploy to the target environment, paired with a rollback job mirroring the promote workflow |
Expand Down
Loading