A cascade fleet example that exercises two runtime-semantic properties only real GitHub Actions can show: the native GitHub Deployments integration, and the dry-run promote and rollback rehearsals. Both depend on real-GitHub behavior the local act/gitea harness cannot express, which is why they need a live-fleet example repo.
- Native GitHub Deployments. With
deployments.enabled, a real (non-dry-run) orchestrate run deploysstagingand its finalize job creates a real GitHub Deployment through the Deployments API, marks itin_progress, then reports a terminalsuccessstatus carrying the environment'senvironment_url. act and gitea have no Deployments API, so only real GitHub can observe the Deployment record and its status. - Dry-run promote is a rehearsal. A promote dispatched with
dry_run=trueconcludes success while itspromote,deploy, and finalize-mutation steps are skipped, and the committed manifest state, the git tags and releases, and the GitHub Deployments are all left unchanged. - Dry-run rollback is a rehearsal. A rollback dispatched with
dry_run=trueresolves and prints its target, concludes success, and skips itsdeployandfinalizejobs, leaving state, tags, and Deployments unchanged.
The "a dry run created no Deployment" assertions are real-GitHub-only checks: the native Deployment steps are gated on a non-dry-run run, and only a repo with a live Deployments API can prove a dry run created no Deployment record.
Two environments, each with an environment_url, and one deploy:
| Field | Value |
|---|---|
environments |
staging (https://staging.example.com), prod (https://app.example.com) |
deployments |
enabled: true, keep_prior_active: true |
deploys |
app (callee deploy-app.yaml) |
builds |
build stub, so the promote chain is valid |
state.prod carries a single deploy-history ring entry. That entry gives the
dry-run rollback a resolvable prior target (resolved from the ring, no git
lookup), so the rollback rehearsal can run and be asserted non-mutating without
first establishing two real prod deployments on every suite run. state.staging
starts empty; the suite's real orchestrate run establishes it (and the positive
native-Deployment proof) at run time.
scenario-suite.yaml runs one real lifecycle and two dry-run rehearsals:
- merges a source change to drive a real orchestrate run, then asserts a native
stagingDeployment was created and concludedsuccess; - dispatches
promotewithdry_run=trueand asserts the run is green, the mutating jobs are skipped, and state, tags, releases, and Deployments are unchanged; and - dispatches
cascade-rollbackwithdry_run=trueand asserts the run is green, the mutating jobs are skipped, and state, tags, and Deployments are unchanged.
Every run the suite causes is recorded in the fleet ledger, and the final reconcile job fails closed on any unregistered run.
.github/manifest.yaml cascade manifest (config plus live state)
.github/workflows/build.yaml build callee stub
.github/workflows/deploy-app.yaml deploy callee
.github/workflows/orchestrate.yaml generated CI on trunk merges
.github/workflows/promote.yaml generated environment promotion
.github/workflows/cascade-rollback.yaml generated rollback
.github/workflows/cascade-hotfix.yaml generated hotfix
.github/workflows/scenario-suite.yaml driver and assertions
The orchestrate, promote, hotfix, and rollback workflows and the
manage-release action are generated by cascade from the manifest. Run
cascade generate-workflow after editing the manifest.