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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ cascade generates workflows that handle the orchestration layer. Your callback w
- **Dispatch inputs**: expose operator-facing manual-run inputs on the generated `workflow_dispatch`.
- **PR plan preview**: a comment on each PR shows which builds and deploys would run.
- **Merge queue lane**: a dedicated gate job runs before merge to protect trunk.
- **Action pinning**: `pin_mode: sha` emits pinned SHA references for all cascade-managed action calls. Override individual actions via `action_pins`.
- **Action pinning**: `pin_mode: sha` emits pinned SHA references for all cascade-managed action calls. Override individual actions via `action_pins`. cascade owns the action pins in the workflows it generates, and the opt-in `reconcile` companion reconciles an external bump (for example a merged Dependabot update) back into the manifest so ownership stays in one place.
- **Breaking-change gate**: `feat!:` or `BREAKING CHANGE:` commits block the prerelease-to-release boundary unless you override them.
- **Artifact passing**: the `artifact_id` output from build callbacks is stored in state and forwarded to deploys and the publish callback.
- **Publish callback**: once a release is published, a separate workflow call lets you retag RC artifacts in your registry.
Expand Down
41 changes: 35 additions & 6 deletions docs/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Generated workflows are build output. cascade owns the third-party action pins i

A future cascade version may write a pointer comment into the generated workflow header naming the manifest that owns its pins, so ownership is visible from the file itself without consulting these docs. That pointer is not emitted today; nothing in the current output implies it.

cascade is also adding an opt-in reconcile companion that watches for an external action-pin change (for example a Dependabot bump landing in a generated workflow) and adopts it into `action_pins` automatically, then regenerates so every workflow agrees again. It has not shipped yet; the sections below on Dependabot, token permissions, and automerge describe the ownership model it is built on and the fallback posture to use until it does.
cascade also ships an opt-in reconcile companion that watches for an external action-pin change (for example a Dependabot bump landing in a generated workflow) and adopts it into `action_pins` automatically, then regenerates so every workflow agrees again. Set `reconcile.enabled: true` to emit it; see [Reconcile companion](#reconcile-companion-opt-in) below for the generated shape and [reconcile](/cli-reference/#reconcile) for the command it runs. The sections below on Dependabot, token permissions, and automerge describe the ownership model it is built on and the fallback posture for repositories that do not opt in.

Two fields control the pinning policy today.

Expand Down Expand Up @@ -183,17 +183,17 @@ Setting `action_pins` for an action switches that action's update channel. Befor

One consequence of that: because the override is the only state, an adopted pin can trail cascade's own curated default over time, for example when cascade's table later moves the same action to a newer commit. This is a known, documented edge today, not something cascade reconciles automatically.

#### Dependabot fallback (until the reconcile companion ships)
#### Dependabot fallback (for repositories that do not enable reconcile)

Dependabot can propose bumps directly against the actions pinned in your generated workflow files, since it reads `uses:` lines wherever they appear. Until the reconcile companion is available, the practical fallback is excluding the generated workflow paths from Dependabot's GitHub Actions directory scan, so a bump lands in `action_pins` where cascade tracks it instead of a hand-edit that the next regenerate reports as drift. Treat this as a fallback, not the recommended posture: once the reconcile companion ships, prefer letting it adopt the bump into the manifest rather than steering Dependabot away from the generated paths.
Dependabot can propose bumps directly against the actions pinned in your generated workflow files, since it reads `uses:` lines wherever they appear. For a repository that does not enable the reconcile companion, the practical fallback is excluding the generated workflow paths from Dependabot's GitHub Actions directory scan, so a bump lands in `action_pins` where cascade tracks it instead of a hand-edit that the next regenerate reports as drift. Treat this as a fallback, not the recommended posture: prefer enabling `reconcile` so the companion adopts the bump into the manifest rather than steering Dependabot away from the generated paths.

#### Token permissions for pin ownership (forward-looking)
#### Token permissions for pin ownership

The token that writes manifest state (`state_token`, or its `_app` variant; see [Token authentication](#token-authentication)) needs headroom for pin ownership. It already needs `Contents: write` to push manifest state. The reconcile companion will add `Workflows: write`, because pushing a regenerated `.github/workflows/*.yaml` file requires the workflow scope; without it, the push fails. The fuller set a token exercising cascade's pin ownership, hotfix, drift-check, and deployment features needs is `Metadata: read`, `Contents: read and write`, `Workflows: write`, `Actions: read and write`, `Pull requests: read and write`, `Issues: read and write`, and `Deployments: read and write`. Provisioning this set once, through a GitHub App installation token or a fine-grained PAT, avoids re-scoping every time a new feature lands. A broad classic PAT can express the same permissions but without per-repo or per-scope precision, so prefer the App or fine-grained PAT path.
The token that writes manifest state (`state_token`, or its `_app` variant; see [Token authentication](#token-authentication)) needs headroom for pin ownership. It already needs `Contents: write` to push manifest state. The reconcile companion adds `Workflows: write` when a regenerate must also push updated workflow files, because that requires the workflow scope; without it, the push fails. The fuller set a token exercising cascade's pin ownership, hotfix, drift-check, and deployment features needs is `Metadata: read`, `Contents: read and write`, `Workflows: write`, `Actions: read and write`, `Pull requests: read and write`, `Issues: read and write`, and `Deployments: read and write`. Provisioning this set once, through a GitHub App installation token or a fine-grained PAT, avoids re-scoping every time a new feature lands. A broad classic PAT can express the same permissions but without per-repo or per-scope precision, so prefer the App or fine-grained PAT path.

#### Automerge caveat

Enabling the reconcile companion will change what a red drift check means. Today, a hand-edited pin or an external bump landing in a generated file makes `cascade verify` fail and stays red until someone intervenes. With the companion enabled, that same bump is instead adopted into `action_pins` and the workflow regenerated automatically, turning what would have been a red check green. If your repository automerges once checks pass, a pin bump can land and merge unattended. If you rely on automerge, prefer the companion's followup commit-routing mode when it ships: it opens the adoption as its own pull request rather than pushing onto the triggering one, so a human still reviews the pin change before it merges.
Enabling the reconcile companion changes what a red drift check means. Without it, a hand-edited pin or an external bump landing in a generated file makes `cascade verify` fail and stays red until someone intervenes. With the companion enabled, that same bump is instead adopted into `action_pins` and the workflow regenerated automatically, turning what would have been a red check green. If your repository automerges once checks pass, a pin bump can land and merge unattended. If you rely on automerge, prefer the companion's followup commit-routing mode (`reconcile.commit: followup`): it opens the adoption as its own pull request rather than pushing onto the triggering one, so a human still reviews the pin change before it merges.

### Token authentication

Expand Down Expand Up @@ -598,6 +598,35 @@ Behavior:

> **Pin recommendation.** When you enable `comment: true`, consider setting `pin_mode: sha`. The comment companion runs `actions/github-script` in a write-scoped `workflow_run` job, and the product default `pin_mode: tag` references that action by a floating major tag. Pinning to a full commit SHA removes the floating-tag exposure on the one job that holds a `pull-requests: write` token.

### Reconcile companion (opt-in)

Set `reconcile.enabled: true` and `generate-workflow` emits the fork-safe [`cascade reconcile`](/cli-reference/#reconcile) lane: a `pull_request` detector plus a `workflow_run` companion that adopts an external governed-pin change back into `action_pins` and regenerates, so a bump such as a merged Dependabot update lands in the manifest instead of drifting the generated workflow out from under it.

```yaml
ci:
config:
reconcile:
enabled: true
source: dependabot
commit: append
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | bool | false | Emit the reconcile detector (`.github/workflows/cascade-reconcile-check.yaml`) and companion (`.github/workflows/cascade-reconcile-companion.yaml`) |
| `source` | string | `dependabot` | The change-source adapter this companion recognizes. The reconcile engine itself is source-agnostic; `dependabot` is the first adapter. |
| `commit` | string | `append` | How the adoption commit is routed. `append` pushes onto the triggering pull request's own branch; `followup` opens a separate pull request instead, for repositories that automerge without further review. |

Behavior:

- **Opt-in and additive.** Omit `reconcile` and nothing is emitted; existing output is byte-for-byte identical to before.
- **Read-only detector.** `cascade-reconcile-check.yaml` triggers on `pull_request` with `contents: read` only. It runs `cascade reconcile --check`, which decides relevance and writes the changed governed refs to a data-only `pin-reconcile-result` artifact; a fork pull request gets a read-only token and no secrets, so this job cannot push or comment.
- **Base-definition companion.** `cascade-reconcile-companion.yaml` triggers on `workflow_run` in the base-repo context, where it holds a scoped `contents: write` / `pull-requests: write` token. It resolves the target pull request only from trusted `workflow_run` run metadata, downloads the detector's artifact as data, fetches the pull request's head files via the trusted `refs/pull/<n>/head` ref (never a checkout of a fork's own repository), and runs the pinned `cascade reconcile` binary to adopt the change.
- **Commit routing.** `commit: append` (the default) pushes the adoption commit onto the triggering pull request's own branch, but only when that pull request is not a fork; a fork pull request always falls back to a sticky comment naming the refs to adopt by hand, since cascade cannot push to a fork's branch. `commit: followup` never touches the original branch: it commits to a cascade-owned `cascade-reconcile/pr-<n>` branch and opens (or updates) a separate pull request against the same base, so an automerge-without-review pull request is never mutated in place.
- **Loop guards.** The companion only pushes when `cascade reconcile` actually changed something, re-checks the branch's fresh tip before pushing and aborts rather than overwriting newer commits, and never force-pushes onto a shared branch.
- **Automerge caveat.** See [Automerge caveat](#automerge-caveat) above: enabling this companion turns a would-be-red drift into a green check, so prefer `commit: followup` if your repository automerges once checks pass.
- **Token scope.** The common case needs only `Contents: write` on the state token, because the source pull request already updated the generated workflow byte for byte and only the manifest changes; `Workflows: write` is needed only when a regenerate must also push updated workflow files.

### Native deployments (opt-in)

Set `deployments.enabled: true` and the finalize job reports deployment status through the [GitHub Deployments API](https://docs.github.com/en/rest/deployments/deployments). It creates a Deployment for the environment selected at run time, marks it `in_progress`, then reports a terminal `success` or `failure` status once the deploy callbacks finish. Pair it with a per-environment `environment_url` so the Deployment status links straight to the running environment.
Expand Down
2 changes: 2 additions & 0 deletions docs/src/content/docs/coverage-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ only under real installation tokens on the fleet, never in the token-free harnes
| Rollback to prior version or SHA | `rollback/*` (8 scenarios) | `probe_rollback` (4env), `rollback-check` (2env) | `internal/rollback` | An env rewinds, is marked diverged, and the ring snapshot advances |
| External rollback via `repository_dispatch` | | repository_dispatch rollback, state revert asserted (rollback-dispatch) | `internal/rollback` | A real `repository_dispatch` payload drives the automated rollback entry point and the target env's state is read back reverted |
| Drift check and comment | `22-verify-drift`, `27-verify-orphan`, `28-drift-check` | `probe_drift` (4env) | `internal/verify`, `internal/generate` | Generated-vs-committed drift is detected and surfaced on a real run |
| Reconcile: adopt a governed pin bump | `pin_reconcile_test.go` (`TestReconcileAdoptsBumpAndSurvivesRegen`) | | `internal/pinreconcile`, `internal/generate` | An external governed-pin bump lands in the manifest's `action_pins` and survives a regenerate |
| Reconcile companion (emitted detector plus workflow_run companion) | `reconcile_companion_test.go` (unit plus actionlint across commit modes) | | `internal/generate` | The emitted detector and workflow_run companion are well-formed, resolve the pull request from trusted `workflow_run` metadata, and route the adoption commit by pull-request location and mode, with real-run proof from cascade's own self-heal companion and the fleet |
| Validate gate | `14-validate-check`, `17-validate-callback` | `probe_validate` (4env); pre-build validate gate (3env) | `internal/generate` | A validate callback gates the build before it proceeds |
| Merge queue | `15-merge-queue` | `probe_merge_queue` (4env) | `internal/generate` | The merge-queue lane is emitted and runs (harness covers the no-configured-queue case) |
| Pull-request preview | `16-pr-preview` | `pr-preview-check` (2env) | `internal/generate` | The preview run fires on a PR and posts its comment |
Expand Down
21 changes: 21 additions & 0 deletions docs/src/content/docs/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,27 @@ gh api repos/my-org/my-repo/dispatches \

The event type must match one of the configured `types`. Because the trigger fires the same N-1 rollback the manual path performs, the dispatching system needs no rollback logic of its own.

## Reconcile companion

Set `reconcile.enabled: true` (see [Reconcile companion](/configuration/#reconcile-companion-opt-in) in the configuration reference) and cascade emits an opt-in, fork-safe lane that watches for an external governed action-pin change and adopts it back into the manifest.

`cascade-reconcile-check.yaml` is the detector: a `pull_request` job that runs with `contents: read` only, so a fork pull request gets a read-only token and no secrets. It runs the real `cascade reconcile --check` command against the pull request's changed workflow files, which decides relevance and writes the changed governed refs to a data-only `pin-reconcile-result` artifact. It never pushes or comments.

`cascade-reconcile-companion.yaml` is the base-definition companion: an `on: workflow_run` job that fires once the detector completes, running in the base repository's context with a scoped `contents: write` / `pull-requests: write` token rather than whatever posture the (possibly fork) pull request carries. Its steps:

1. **Trusted PR resolution.** The target pull request is derived only from the triggering `workflow_run`'s own metadata (its `pull_requests` array, or a head-SHA lookup for a fork pull request), then re-fetched fresh from the API. The companion never trusts the detector's artifact for the pull request number, and it aborts rather than reconciling stale data if the pull request's head has moved since the source run started.
2. **Relevance trigger.** The companion downloads the detector's artifact as data and no-ops when it reports no governed change, so an irrelevant pull request costs nothing beyond the read-only detector.
3. **Head-as-data checkout.** The pull request's head is fetched via the trusted `refs/pull/<n>/head` ref on the base repository, never a direct checkout of a fork's own repository, so nothing from a fork's own configuration is ever executed.
4. **Pinned-binary execution.** The companion installs a pinned release build of the cascade CLI (the same `setup-cli` action every generated workflow uses) rather than building or running off the repository's own source, so a pull request cannot smuggle in a modified reconcile implementation.
5. **Real, idempotent adoption.** It runs the actual `cascade reconcile` command against the changed files, the same command a maintainer could run by hand, so a converged tree is a real no-op rather than a scripted approximation.
6. **Commit routing.** `commit: append` (the default) pushes the adoption commit directly onto the pull request's own branch, but only when that pull request is not a fork; a fork pull request always falls back to a sticky comment naming the refs to adopt by hand, since cascade has no push access to a fork's branch. `commit: followup` never touches the original branch at all: it commits to a cascade-owned `cascade-reconcile/pr-<n>` branch and opens (or updates) a separate pull request against the same base, which is the recommended posture for a repository that automerges once checks pass.

Three loop-termination guards keep the companion from ever looping on itself: it pushes only when the real reconcile command actually changed something (a converged tree pushes nothing), it re-checks the branch's fresh tip immediately before pushing and aborts rather than overwriting commits made since the run started, and it never force-pushes onto a branch it does not own.

**Token requirement.** The common case needs only `Contents: write` on the token that pushes the adoption commit, because the triggering pull request already updated the generated workflow byte for byte and only the manifest's `action_pins` entry changes underneath it. `Workflows: write` is needed only when a regenerate must also push updated `.github/workflows/*.yaml` files, which is the same token headroom the [Action pinning](/configuration/#action-pinning) section describes.

**Honest automerge caveat.** Enabling this companion turns what would have been a red drift check into a green one: an external pin bump that used to require a human to intervene is instead adopted and pushed automatically. A repository that automerges once checks pass can therefore merge a pin bump unattended. Prefer `commit: followup` if that matters to you; it opens the adoption as its own pull request so a human still reviews the change before it merges.

## Workflow Permissions

Generated workflows include the necessary permissions:
Expand Down
Loading