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
4 changes: 2 additions & 2 deletions .github/actions/setup-cli/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ runs:
# Robustly capture a single-line version, sanitized for GHA's output
# file format. Older form `echo "version=$RAW" >> $GITHUB_OUTPUT`
# produced `##[error]Invalid format 'unknown'` against the v1.1.0
# binary likely a GHA file-command parser quirk where certain
# values get mis-interpreted as a format directive. Strip CR/newlines
# binary (likely a GHA file-command parser quirk where certain
# values get mis-interpreted as a format directive). Strip CR/newlines
# and non-printable characters, fall back to "unknown" on empty/error,
# cap length, and use printf instead of echo to avoid backslash
# interpretation surprises.
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# End-to-end test workflow
# Triggers:
# push:tags every release tag (existing)
# workflow_dispatch manual run against any ref (existing)
# merge_group runs as a merge-queue gate before merging to main
# schedule nightly at 07:00 UTC (low-traffic window) against main
# push:tags every release tag (existing)
# workflow_dispatch manual run against any ref (existing)
# merge_group runs as a merge-queue gate before merging to main
# schedule nightly at 07:00 UTC (low-traffic window) against main
#
# E2E uses act + gitea testcontainers and is too slow + flaky to run per PR.
# Run locally (`go test -v ./e2e/...`) before pushing instead.
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
# enough headroom. Override per-dispatch as needed.
E2E_TIMEOUT: ${{ github.event.inputs.timeout || '60m' }}
# Cap subtest parallelism. The GitHub runner has 4 cores / ~7.9GB
# RAM. Each scenario spins up gitea + act + N job containers at
# RAM. Each scenario spins up gitea + act + N job containers; at
# the default GOMAXPROCS=4, four scenarios concurrently exhaust
# memory and the test process is OOM-killed (silent FAIL with no
# per-test output, see #104).
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pull Request validation workflow
# Runs unit tests + lint on PRs. E2E runs separately (e2e.yaml) on
# release tags and manual dispatch — too slow + flaky to run per PR.
# release tags and manual dispatch. Too slow and flaky to run per PR.
#
# This workflow triggers on every PR to main so the PR Gate job always
# reports a status. The expensive jobs (Unit Tests, Lint) are gated on a
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/promote.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.release-data.outputs.sem_version }}
run: |
# Only dispatch on real GitHub — in act/gitea e2e environments
# Only dispatch on real GitHub. In act/gitea e2e environments
# GITHUB_SERVER_URL is http://gitea:3000 and the Release workflow
# doesn't exist, so skip silently.
if [[ "$GITHUB_SERVER_URL" != "https://github.com" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
release:
types: [published, released]
# Explicitly triggered by the promote workflow's finalize job after
# publishing. This is the reliable path event-based triggers don't
# publishing. This is the reliable path; event-based triggers don't
# fire when releases are managed via API calls in a workflow run.
workflow_dispatch:

Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ release that bumps `schema_version`.
`actions/upload-artifact` v7, `goreleaser/goreleaser-action` v7,
`github.com/spf13/cobra` v1.10.2. (#48, #49, #52, #53, #54)

## [0.1.0] 2026-06-09
## [0.1.0] - 2026-06-09

Initial release of cascade: a trunk-based CI/CD orchestrator for GitHub Actions.

Expand Down
48 changes: 24 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<h1 align="center">cascade</h1>

<!-- TODO(image): logo place at docs/images/logo.png once generated -->
<!-- TODO(image): logo. place at docs/images/logo.png once generated -->
<!-- ![cascade logo](docs/images/logo.png) -->

<!-- Row 1: CI/quality -->
Expand Down Expand Up @@ -31,7 +31,7 @@

## How it works

<!-- TODO(image): hero/architecture diagram place at docs/images/architecture.png once generated -->
<!-- TODO(image): hero/architecture diagram. place at docs/images/architecture.png once generated -->
<!-- ![cascade architecture](docs/images/architecture.png) -->

The **manifest** (`.github/manifest.yaml`) is the single source of truth. It holds both the pipeline configuration and the live deployment state for every environment. You run `cascade generate-workflow` once; after that the generated workflows own their own execution.
Expand Down Expand Up @@ -64,7 +64,7 @@ Merge to trunk
└─────────────────────────────────────────────────────────────┘
```

<!-- TODO(image): promote/cascade flow diagram place at docs/images/promote-flow.png once generated -->
<!-- TODO(image): promote/cascade flow diagram. place at docs/images/promote-flow.png once generated -->
<!-- ![cascade promotion flow](docs/images/promote-flow.png) -->

---
Expand Down Expand Up @@ -146,20 +146,20 @@ cascade is a metadata courier. You construct the registry and deploy operations

cascade generates workflows that handle the orchestration layer. Your callback workflows handle the domain logic. The manifest gives you control over:

- **Change detection**builds and deploys only run when their declared `triggers` match changed paths
- **Dependency ordering**`depends_on` chains builds and deploys in the right order
- **Matrix builds**fan out a single build over a matrix of inputs
- **Per-job runner selection** `runs_on` at the config or per-build/deploy level
- **Concurrency control**configurable group and cancel-in-progress on orchestrate, promote, release, and external-update workflows
- **Extra triggers**attach `schedule`, `repository_dispatch`, `workflow_run`, and `merge_group` events to orchestration
- **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`
- **Breaking-change gate**`feat!:` or `BREAKING CHANGE:` commits block the prerelease-to-release boundary unless explicitly overridden
- **Artifact passing** `artifact_id` output from build callbacks is stored in state and forwarded to deploys and the publish callback
- **Publish callback** — after a release is published, a separate workflow call lets you retag RC artifacts in your registry
- **Schema version enforcement**`schema_version` on the manifest is checked on every CLI invocation; incompatible manifests are rejected with a clear error
- **Change detection**: builds and deploys run only when their declared `triggers` match changed paths.
- **Dependency ordering**: `depends_on` chains builds and deploys in the right order.
- **Matrix builds**: fan out a single build over a matrix of inputs.
- **Per-job runner selection**: set `runs_on` at the config or per-build/deploy level.
- **Concurrency control**: configurable group and cancel-in-progress on orchestrate, promote, release, and external-update workflows.
- **Extra triggers**: attach `schedule`, `repository_dispatch`, `workflow_run`, and `merge_group` events to orchestration.
- **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`.
- **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.
- **Schema version enforcement**: every CLI invocation checks `schema_version` on the manifest and rejects incompatible manifests with a clear error.

For a no-environment project (library or CLI), omit `environments` entirely. Commits produce RC pre-releases; a `promote` dispatch publishes the final release.

Expand Down Expand Up @@ -250,11 +250,11 @@ Full flag reference: [docs/cli-reference.md](docs/cli-reference.md).

## Roadmap to stable

cascade is functional and self-hosted — the releases page shows the full pipeline running end to end. The remaining work before the v1.0.0 schema freeze falls into two areas:
cascade is functional and self-hosted. Its own releases page shows the full pipeline running end to end. The remaining work before the v1.0.0 schema freeze falls into two areas.

**Schema coverage** — a handful of GitHub Actions capabilities are modeled in the manifest shape but not yet emitted by the generator: environment gates, OIDC token configuration, and per-environment runner overrides. These are on the direct path to v1.0.0.
**Schema coverage.** A few GitHub Actions capabilities are modeled in the manifest shape but not yet emitted by the generator: environment gates, OIDC token configuration, and per-environment runner overrides. These sit on the direct path to v1.0.0.

**Hardening** schema version enforcement (shipped), compatibility docs ([docs/versioning.md](docs/versioning.md)), and further e2e coverage to confirm that the generated workflows behave correctly under edge cases (empty builds, cross-repo coordination, rollback to N-1).
**Hardening.** This covers schema version enforcement (shipped), compatibility docs ([docs/versioning.md](docs/versioning.md)), and more e2e coverage. The added tests confirm that the generated workflows behave correctly under edge cases such as empty builds, cross-repo coordination, and rollback to N-1.

The manifest schema field shapes were frozen in v0.1.0 as the v1 contract baseline. Minor versions between now and v1.0.0 may add new optional fields; no existing fields will be removed or renamed before v1.0.0.

Expand All @@ -266,10 +266,10 @@ Open work is tracked in [GitHub Issues](https://github.com/stablekernel/cascade/

cascade follows these conventions in its own codebase and in the generated workflows it produces:

- **Additive manifest changes**new fields are always optional with sensible defaults; existing manifest files continue to work across minor version bumps
- **Conventional commits**commit messages follow `type: subject` (e.g., `feat:`, `fix:`, `docs:`); the changelog generator reads this format
- **Callback isolation**generated workflows call your workflows via `workflow_call`; cascade never reaches into your callback logic
- **Metadata courier**cascade passes artifact identifiers and versions between stages; it never touches your container registry, package registry, or deployment target directly
- **Additive manifest changes**: new fields are always optional with sensible defaults, so existing manifest files keep working across minor version bumps.
- **Conventional commits**: commit messages follow `type: subject` (for example `feat:`, `fix:`, `docs:`), and the changelog generator reads this format.
- **Callback isolation**: generated workflows call your workflows via `workflow_call`, and cascade never reaches into your callback logic.
- **Metadata courier**: cascade passes artifact identifiers and versions between stages. It never touches your container registry, package registry, or deployment target directly.

---

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Version | Supported |
| --- | --- |
| 0.x (latest) | Yes security fixes backported promptly |
| 0.x (latest) | Yes, security fixes backported promptly |
| Older 0.x tags | No |

The `0.x` line is the active release line. Only the most recent tag receives
Expand Down
32 changes: 16 additions & 16 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ System design and internals of cascade.

## Design Principles

1. **Build once, deploy everywhere** - Single artifact promoted through environments
2. **Change-driven** - Only build/deploy what changed
3. **Trunk-based** - Single main branch, short-lived feature branches
4. **Callback contract** - Framework orchestrates, adopting repos own build/deploy
5. **State tracking** - Manifest tracks what's deployed where
1. Build once, deploy everywhere. One artifact is promoted through every environment.
2. Change-driven. We build and deploy only what changed.
3. Trunk-based. A single main branch backs short-lived feature branches.
4. Callback contract. The framework orchestrates and adopting repos own build and deploy.
5. State tracking. The manifest records what is deployed where.

## System Overview

Expand Down Expand Up @@ -561,32 +561,32 @@ For satellite repos with notify config:
The generator emits an `environment: <name>` key on each deploy job whenever the
manifest includes an `environments` list. That single key is enough for GitHub
Actions to attach deployment records, honour required-reviewer gates, apply
wait timers, and scope environment secretsall configured inside GitHub, not
in the manifest. No cascade code calls the Deployments REST API or the
Environments REST API directly.
wait timers, and scope environment secrets. You configure all of that inside
GitHub, not in the manifest. No cascade code calls the Deployments REST API or
the Environments REST API directly.

### What is deferred

Two capabilities are intentionally out of scope for v1:

- **Programmatic Deployments API status** — cascade does not currently call
- Programmatic Deployments API status. cascade does not call
`POST /repos/{owner}/{repo}/deployments` or
`POST /repos/{owner}/{repo}/deployments/{id}/statuses`. GitHub Actions creates
these records automatically when a job carries `environment:`, so adopters get
deployment records for free without cascade owning that call.
deployment records without cascade owning that call.

- **Environments REST configuration sync** — cascade does not read or write
- Environments REST configuration sync. cascade does not read or write
environment protection rules (required reviewers, wait timers, branch policies)
via the REST API. That configuration lives in GitHub today.

### Why deferred

Keeping cascade out of these APIs in v1 bounds the surface area and avoids
coupling the tool to GitHub API semantics that are still evolving. The
auto-created deployment records from `environment:` already satisfy the common
case; adding programmatic control before there is a clear adopter need would
add complexity without a demonstrable benefit. If those APIs change shape,
cascade would need to track the change even though nothing in v1 depends on them.
auto-created deployment records from `environment:` already cover the common
case. Adding programmatic control before an adopter needs it would buy
complexity and nothing else. If those APIs change shape, cascade would have to
track the change even though nothing in v1 depends on them.

### How the design reserves the extension points

Expand All @@ -598,7 +598,7 @@ without a breaking change:

```yaml
config:
environments: [dev, test, prod] # ordered list source of truth, unchanged
environments: [dev, test, prod] # ordered list (source of truth), unchanged
environment_config: # reserved; omitting it is valid today
prod:
gha_environment: production # maps to the GHA environment name
Expand Down
2 changes: 1 addition & 1 deletion docs/callback-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ jobs:
docker push myrepo/${{ inputs.build_name }}:${{ inputs.new_version }}
```

The framework only carries metadata. The publish callback is responsible for the registry operation. If `artifact_id` is provided, prefer it over `old_version` for unambiguous identification.
The framework only carries metadata. The publish callback performs the registry operation. When `artifact_id` is present, use it instead of `old_version` so the target is unambiguous.

## Custom Inputs

Expand Down
12 changes: 6 additions & 6 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Complete reference for the `cascade` command-line tool.
# Latest stable
go install github.com/stablekernel/cascade/cmd/cascade@latest

# Bleeding edge from master
# Latest build from master
go install github.com/stablekernel/cascade/cmd/cascade@master

# Specific version
Expand All @@ -35,7 +35,7 @@ These flags are available on all commands:

| Flag | Type | Description |
|------|------|-------------|
| `--dry-run` | bool | Preview mode show what would happen without making changes |
| `--dry-run` | bool | Preview mode: show what would happen without making changes |
| `--trace` | bool | Enable TRACE-level logging for detailed internals |
| `--json` | bool | Output structured JSON for workflow consumption |

Expand Down Expand Up @@ -189,7 +189,7 @@ cascade generate-workflow
- **Output chaining**: passes outputs from one callback to dependents
- **Per-callback policies**: respects `run_policy`, `on_failure`, `retries`
- **Environment overrides**: applies `env_inputs` per environment
- **Publish step**: when `publish:` is configured, the promote workflow dispatches the callback once per build at the prerelease→release boundary
- **Publish step**: when `publish:` is configured, the promote workflow dispatches the callback once per build at the boundary where a prerelease becomes a release

### manage-release

Expand Down Expand Up @@ -390,9 +390,9 @@ cascade next-version \
| `--json` | bool | No | Output as JSON |

Bump rules:
- Breaking change (`feat!`, `BREAKING CHANGE:`) major bump
- Feature (`feat`) minor bump
- Fix (`fix`) patch bump
- Breaking change (`feat!`, `BREAKING CHANGE:`) triggers a major bump
- Feature (`feat`) triggers a minor bump
- Fix (`fix`) triggers a patch bump
- Pre-release environments append an RC suffix (e.g., `v1.3.0-rc.0`)

### external
Expand Down
Loading
Loading