cpflow 6.0.0.rc.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/agent-workflow.yml +16 -38
  3. data/.agents/bin/README.md +7 -8
  4. data/.agents/legacy-workflow-policy.yml +11 -0
  5. data/.agents/shaka.md +20 -0
  6. data/.agents/trusted-actions.yml +12 -0
  7. data/.coderabbit.yaml +1 -0
  8. data/.github/actions/cpflow-build-docker-image/action.yml +153 -22
  9. data/.github/actions/cpflow-delete-control-plane-app/delete-app.sh +3 -0
  10. data/.github/actions/cpflow-preview-promotion/action.yml +82 -0
  11. data/.github/actions/cpflow-preview-promotion/preview-promotion.sh +90 -0
  12. data/.github/workflows/cpflow-cleanup-stale-review-apps.yml +3 -10
  13. data/.github/workflows/cpflow-delete-review-app.yml +4 -11
  14. data/.github/workflows/cpflow-deploy-review-app.yml +19 -14
  15. data/.github/workflows/cpflow-deploy-staging.yml +17 -12
  16. data/.github/workflows/cpflow-promote-staging-to-production.yml +132 -13
  17. data/.github/workflows/rspec.yml +24 -0
  18. data/AGENTS.md +18 -3
  19. data/CHANGELOG.md +26 -3
  20. data/CONTRIBUTING.md +4 -4
  21. data/Gemfile.lock +1 -1
  22. data/README.md +5 -2
  23. data/docs/ai-github-flow-prompt.md +104 -8
  24. data/docs/ci-automation.md +258 -60
  25. data/docs/commands.md +32 -16
  26. data/docs/postgres-review-apps.md +90 -0
  27. data/docs/postgres.md +233 -375
  28. data/docs/secrets-and-env-values.md +42 -0
  29. data/docs/sidebars.ts +1 -0
  30. data/lib/command/ai_github_flow_prompt.rb +10 -13
  31. data/lib/command/ai_github_flow_prompt.txt +103 -0
  32. data/lib/command/apply_template.rb +36 -4
  33. data/lib/command/base.rb +56 -3
  34. data/lib/command/delete.rb +174 -4
  35. data/lib/command/deploy_image.rb +6 -0
  36. data/lib/command/generate.rb +247 -3
  37. data/lib/command/generate_github_actions.rb +15 -17
  38. data/lib/command/setup_app.rb +92 -10
  39. data/lib/command/update_github_actions.rb +107 -43
  40. data/lib/core/config.rb +73 -0
  41. data/lib/core/controlplane.rb +22 -2
  42. data/lib/core/controlplane_api.rb +28 -2
  43. data/lib/core/controlplane_api_direct.rb +17 -0
  44. data/lib/core/disposable_postgres_helpers.rb +167 -0
  45. data/lib/core/doctor_service.rb +7 -0
  46. data/lib/core/github_flow_readiness/checks.rb +2 -1
  47. data/lib/core/github_flow_readiness_service.rb +4 -0
  48. data/lib/core/repo_introspection.rb +112 -2
  49. data/lib/cpflow/version.rb +1 -1
  50. data/lib/generator_templates/.dockerignore +16 -0
  51. data/lib/generator_templates/Dockerfile +9 -5
  52. data/lib/generator_templates/entrypoint.sh +2 -0
  53. data/lib/generator_templates/templates/postgres.yml +9 -0
  54. data/lib/generator_templates_sqlite/controlplane.yml +2 -1
  55. data/lib/generator_templates_sqlite/release_script.sh +1 -1
  56. data/lib/generator_templates_sqlite/templates/rails.yml +2 -1
  57. data/lib/github_flow_templates/.github/cpflow-help.md +39 -12
  58. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +1 -0
  59. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +1 -0
  60. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +132 -13
  61. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +37 -13
  62. data/lib/github_flow_templates/bin/test-cpflow-github-flow +88 -47
  63. metadata +12 -3
@@ -23,7 +23,7 @@ End-to-end rollout in one view:
23
23
 
24
24
  1. `cpflow github-flow-readiness` — exits non-zero if the repo is not ready to deploy.
25
25
  2. `cpflow generate` — creates `.controlplane/` if missing.
26
- 3. `cpflow generate-github-actions` — adds `cpflow-*` workflow wrappers and `.github/actions/cpflow-*` composite actions. Review-app, staging, cleanup, and helper workflows call upstream reusable workflows; production promotion is a normal caller-repo job so it can own the protected production Environment.
26
+ 3. `cpflow generate-github-actions` — adds `cpflow-*` workflow wrappers. Review-app, staging, cleanup, and helper workflows call upstream reusable workflows; production promotion is a normal caller-repo job so it can own the protected production Environment.
27
27
  4. Configure the [GitHub Actions secrets and variables](#github-actions-secrets-and-variables) the workflows expect.
28
28
  5. Push the branch, then comment `+review-app-deploy` on a PR to spin up a review environment.
29
29
 
@@ -69,7 +69,6 @@ The second command writes namespaced files so they can coexist with an app's exi
69
69
  - `.github/workflows/cpflow-deploy-staging.yml`
70
70
  - `.github/workflows/cpflow-promote-staging-to-production.yml`
71
71
  - `.github/workflows/cpflow-cleanup-stale-review-apps.yml`
72
- - `.github/actions/cpflow-*` (the local composite actions used by those workflows)
73
72
  - `bin/pin-cpflow-github-ref`
74
73
  - `bin/test-cpflow-github-flow`
75
74
 
@@ -77,7 +76,7 @@ The second command writes namespaced files so they can coexist with an app's exi
77
76
  Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's
78
77
  `Gemfile`, preserves repo-defined frontend precompile hooks such as Shakapacker
79
78
  `precompile_hook` commands or React on Rails auto bundle generation, and
80
- switches to persistent SQLite `db` and `storage` templates when
79
+ switches to persistent SQLite data and Active Storage templates when
81
80
  `config/database.yml` shows SQLite in production.
82
81
 
83
82
  `cpflow github-flow-readiness` checks public RubyGems and npm registry metadata
@@ -230,14 +229,74 @@ because they could change the target org name after normalization.
230
229
 
231
230
  Production promotion copies the exact image currently deployed on the selected
232
231
  staging workload. If that staging image is digest-pinned, the digest is used for
233
- the source copy while the production tag is derived from the tag portion. Tags
234
- with a `_<commit>` suffix keep that suffix in production; plain numeric tags are
235
- also valid and promote to the next plain production tag. The copy step uses
232
+ the source copy while the production tag is derived from the tag portion. Staging tags must
233
+ include a full lowercase 40-character Git commit SHA suffix (`_<commit>`),
234
+ which is preserved in the new production tag. A staging image without that
235
+ provenance stops promotion before approval. The copy step uses
236
236
  `docker buildx imagetools create --prefer-index=false --tag` with isolated
237
237
  Docker credentials, which preserves multi-architecture manifests, preserves
238
238
  single-platform manifest format when supported, and avoids pulling image layers
239
239
  onto the GitHub Actions runner.
240
240
 
241
+ When you start production promotion, the **preview-promotion** job finishes
242
+ before GitHub requests approval for the protected production Environment. Open
243
+ the run summary’s **Commits ready for production** section before approving. It
244
+ compares staging’s deployed primary-workload image with the last recorded
245
+ successful `production-*` GitHub release. It links the full comparison, lists up
246
+ to 100 commit subjects, and flags diverged history or staging behind production.
247
+ Matching SHAs report no commits to promote. Release discovery checks the latest
248
+ 100 releases; missing production history or GitHub API failures produce an
249
+ explicit availability notice. The preview uses the existing staging token and
250
+ GitHub `contents: read` permission; it has no production token.
251
+
252
+ The preview selects the primary workload from the staging app’s
253
+ `app_workloads` configuration. `PRODUCTION_APP_NAME` and
254
+ `CPLN_ORG_PRODUCTION` can remain production Environment variables. When staging
255
+ and production have different primary-workload defaults, set `PRIMARY_WORKLOAD`
256
+ as a repository variable shared by both jobs; the protected job rejects a
257
+ workload-selection mismatch. A Control Plane read failure or invalid staging
258
+ SHA fails the preview and prevents approval from starting.
259
+
260
+ After approval, the protected job checks that staging still uses the captured
261
+ image and, when a release baseline was available, that live production matches
262
+ that release’s commit. A mismatch stops before copying or deploying an image;
263
+ rerun after staging changes, or reconcile the production release record after an
264
+ out-of-band production deployment. If production history was unavailable, the
265
+ summary does not claim to know the production-to-staging diff.
266
+
267
+ Existing repositories adopt this with
268
+ `cpflow update-github-actions --workflows cpflow-promote-staging-to-production.yml`.
269
+ Review and reapply downstream workflow customizations before committing the
270
+ replacement. The bare update command preserves existing top-level workflows.
271
+
272
+ ### Recover a missing or stale production release record
273
+
274
+ A successful production deployment and its GitHub release are separate jobs.
275
+ If deployment succeeded but **create-github-release** failed, open that original
276
+ Actions run and choose **Re-run failed jobs**. This retries release publication
277
+ at the original promoted SHA; do not rerun the whole promotion merely to repair
278
+ the record. Confirm the resulting `production-*` release targets the live
279
+ production image’s SHA, then start a fresh promotion preview.
280
+
281
+ After an out-of-band production deploy or rollback, an app maintainer should
282
+ first verify the healthy live primary-workload image and its full commit suffix
283
+ with `cpln workload get <primary-workload> --gvc <production-app> --org
284
+ <production-org> -o json`. Use that SHA to publish a fresh release record; do not
285
+ move an existing release tag. For example, after replacing `<verified-live-sha>`:
286
+
287
+ ```sh
288
+ gh release create "production-$(date -u +%Y-%m-%d-%H%M%S)-0" \
289
+ --target <verified-live-sha> \
290
+ --title "Production state reconciliation" \
291
+ --notes "Records the verified live production commit after a manual deployment or rollback."
292
+ ```
293
+
294
+ Run this in the app repository with the maintainer’s normal GitHub credentials.
295
+ Verify the new tag’s commit and the live image agree, then rerun promotion to
296
+ produce a fresh preview. If the live image has no commit suffix, establish its
297
+ provenance and use a traceable image before recording a baseline; guessing a
298
+ branch head would make the comparison misleading.
299
+
241
300
  Before copying the image, production promotion compares the environment variable
242
301
  names exposed by staging and production at both the GVC level and each configured
243
302
  app workload's container level. Variables present in staging are treated as
@@ -425,9 +484,11 @@ Recommended org layout:
425
484
  review/staging CI operations; use a dedicated review-app org only if you also customize the generated
426
485
  workflow/configuration to target that org separately
427
486
 
428
- Optional repository secret for private dependency builds:
487
+ Optional repository secrets for Docker builds:
429
488
 
430
489
  - `DOCKER_BUILD_SSH_KEY`: private SSH key used when the Dockerfile needs `RUN --mount=type=ssh` to fetch private GitHub dependencies during image build
490
+ - `DOCKER_BUILD_SECRETS`: staging build credentials as newline-delimited `id=value` entries supplied through BuildKit secret mounts. Values must be single-line; spaces and additional `=` characters are preserved. IDs may contain letters, digits, underscores, and hyphens, and must start with a letter or digit. Empty values and duplicate IDs are rejected. Leave this secret unset when the build does not need credentials.
491
+ - `REVIEW_APP_DOCKER_BUILD_SECRETS`: separate credentials for review-app builds, using the same format. The generated review-app caller uses only this secret; it does not fall back to `DOCKER_BUILD_SECRETS`. Leave it unset to supply no BuildKit secrets to review apps.
431
492
 
432
493
  Optional repository variables for private dependency builds:
433
494
 
@@ -475,12 +536,11 @@ The generated flow uses these defaults:
475
536
  makes the newest intent fail closed instead of falling back to an older operation. This permission gate applies to every
476
537
  `+review-app-deploy` comment, whether or not a review app already exists. Later pushes to a base-repository branch PR
477
538
  redeploy automatically without another approval because the auto-push path (`pull_request` event) does not use the
478
- comment permission gate. The reusable deploy workflow loads generated local actions through `actions/checkout`'s
479
- default with no `ref:` override, which resolves to the commit GitHub recorded for the triggering event: the
480
- pull-request merge revision for automatic same-repository deploys, the selected ref for manual dispatch, or the
481
- default-branch revision for comment triggers. This keeps the wrapper and generated actions synchronized during an
482
- upgrade or first-installation PR. Preserve the same-repository caller guard because pull-request workflow and action
483
- code run with staging/review credentials;
539
+ comment permission gate. The reusable deploy workflow loads its composite actions from `shakacode/control-plane-flow`
540
+ at the commit the wrapper pins, never from the pull request's revision; see
541
+ [Where the actions run from](#where-the-actions-run-from). Preserve the same-repository caller guard because a
542
+ same-repository pull request can still change the wrapper and the application code that run with staging/review
543
+ credentials;
484
544
  - fork pull requests cannot deploy via the generated `pull_request` path because the caller workflow's job-level `if:`
485
545
  condition explicitly skips fork-originated runs. For `issue_comment` events, the caller `if:` restricts invocation to
486
546
  the exact command shape; the reusable workflow resolves repository permission before its deploy job runs, then its
@@ -516,11 +576,10 @@ generated staging/review org and make that token disposable and unable to access
516
576
 
517
577
  The PR-close teardown workflow runs trusted base-branch workflow code with repository secret access so it can delete fork
518
578
  PR review apps. The generated `cpflow-delete-control-plane-app` composite action script refuses to call `cpflow delete`
519
- on any app whose name does not match the review-app prefix. This shell-level guard is effective because the generated
520
- delete workflow loads generated actions through `actions/checkout`'s default with no `ref:` override, which resolves to
521
- the commit GitHub recorded for the triggering event (`GITHUB_SHA`, the base-branch tip under `pull_request_target`). Its
522
- separate app checkout has no `ref:` override either, so `pull_request_target` teardown uses base-branch code for both
523
- paths rather than PR or fork code. If you customize this
579
+ on any app whose name does not match the review-app prefix. This shell-level guard is effective because the delete
580
+ workflow loads that action from the wrapper-pinned `.cpflow` commit. Its separate app checkout has no `ref:` override,
581
+ which resolves to the commit GitHub recorded for the triggering event (`GITHUB_SHA`, the base-branch tip under
582
+ `pull_request_target`), so teardown reads base-branch configuration rather than PR or fork code. If you customize this
524
583
  workflow, never check out PR or fork code in the same job as the delete step; doing so could let a PR replace the guard
525
584
  script itself and would also make `hooks.pre_deletion` come from the PR's `controlplane.yml`. This is still not a token
526
585
  policy, so use a scoped staging service account limited to review/staging operations. A configured
@@ -570,6 +629,30 @@ The generated `cpflow-build-docker-image` action supports this without hardcodin
570
629
  - set `DOCKER_BUILD_SSH_KEY` if the Docker build needs SSH access to GitHub
571
630
  - optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the SSH build host is not GitHub.com or you need custom host entries
572
631
  - set `DOCKER_BUILD_EXTRA_ARGS` when you need extra `docker build` flags
632
+ - set `DOCKER_BUILD_SECRETS` for staging builds that need credentials such as a private package token or a source-map upload token
633
+ - set `REVIEW_APP_DOCKER_BUILD_SECRETS` separately if review-app builds need credentials
634
+
635
+ For example, store the following as the **value** of the `DOCKER_BUILD_SECRETS` GitHub Actions secret:
636
+
637
+ ```text
638
+ sentry_auth_token=<source-map upload token>
639
+ npm_token=<private package token>
640
+ ```
641
+
642
+ The generated staging caller forwards `DOCKER_BUILD_SECRETS`. The review-app caller forwards `REVIEW_APP_DOCKER_BUILD_SECRETS`, the only build-secret parameter the reusable review-app workflow accepts, so review builds never receive the staging secret. The build action writes each value to a private temporary file, passes only its path through `--secret=id=...,src=...`, and removes the files on success or failure. Values are not passed as build arguments or persisted through `GITHUB_ENV`.
643
+
644
+ A Dockerfile can consume a token only for the command that needs it:
645
+
646
+ ```dockerfile
647
+ # syntax=docker/dockerfile:1
648
+ ARG SENTRY_UPLOAD=false
649
+ RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
650
+ SENTRY_UPLOAD=${SENTRY_UPLOAD} npm run build
651
+ ```
652
+
653
+ If the application's build requires an explicit upload opt-in, pass `docker_build_extra_args: --build-arg=SENTRY_UPLOAD=true` in the caller workflow's `with` block, or add that token to `DOCKER_BUILD_EXTRA_ARGS`. The optional workflow input is appended after the repository variable, retaining existing build arguments. Token availability alone need not enable uploads. BuildKit secret values do not invalidate build caches; force the relevant build step to run again when rotating a credential requires repeating an upload.
654
+
655
+ Every secret supplied to a review build is accessible to the PR-controlled Dockerfile, which can read and transmit it. Automatic rebuilds of an existing review app also receive these secrets; approval to create the app does not approve later commits. Configure only credentials whose access can be entrusted to anyone able to change the deployed branch, scoped to the minimum review-build permissions. Separate secret names reduce accidental sharing but do not prevent the Dockerfile from accessing supplied credentials. If a credential cannot be entrusted to branch authors, omit it from review builds or use a customized approval gate that approves each deployed commit. Apply the review-app security guidance above.
573
656
 
574
657
  For example, a repo that installs private dependencies from GitHub during Docker build can set:
575
658
 
@@ -708,18 +791,36 @@ wrapper-level `if:` guard shown in that file, for example
708
791
  ## Upstream Workflows And Actions
709
792
 
710
793
  Most generated workflows are intentionally small wrappers. The deployment
711
- logic and comment formatting live in upstream reusable workflows. The canonical
712
- composite actions live in this repository and the gem copies them into each
713
- downstream repository at `.github/actions/cpflow-*`; the reusable workflows use
714
- those regular local action paths. They separately check out the matching
715
- upstream ref at `.cpflow` for the `cpflow` runtime source. Production promotion
716
- is expanded into the caller repository so it can own `environment: production`,
717
- but follows the same local-action and `.cpflow` source split.
794
+ logic and comment formatting live in upstream reusable workflows, and the
795
+ composite actions they call live in this repository. Each job checks out
796
+ `shakacode/control-plane-flow` at the pinned ref into `.cpflow`, then runs the
797
+ actions as `./.cpflow/.github/actions/cpflow-*` and builds the `cpflow` runtime
798
+ from the same checkout. A downstream repository carries the wrappers and one
799
+ pinned ref, with no copies of the actions. Production promotion is expanded
800
+ into the caller repository so it can own `environment: production`; it pins the
801
+ same source in its own `.cpflow` checkout.
718
802
 
719
803
  - `cpflow-setup-environment`: installs Ruby, the Control Plane CLI, and `cpflow`, then logs into the target org. By default it builds `cpflow` from the checked-out upstream `control-plane-flow` ref; set the `CPFLOW_VERSION` repository variable only when you want to force a published RubyGems release.
720
804
  - `cpflow-build-docker-image`: builds and pushes the app image with the desired commit SHA
721
805
  - `cpflow-delete-control-plane-app`: safely deletes temporary apps and refuses to touch names outside the configured review-app prefix
722
806
 
807
+ ### Where the actions run from
808
+
809
+ The actions that receive `CPLN_TOKEN_STAGING` come from
810
+ `shakacode/control-plane-flow` at the commit the calling wrapper pins. For a
811
+ reusable workflow that is `job.workflow_sha`; for production promotion it is the
812
+ `ref:` of the `Checkout control-plane-flow actions` step.
813
+
814
+ A same-repository pull request can change the wrapper, including its pinned
815
+ ref, and the application code and `.controlplane/` configuration that the
816
+ deployment builds. It cannot change the action code without changing that pin,
817
+ so reviewing the pin reviews the actions. The review-app jobs check out caller
818
+ files only under `app/`, and never at the workspace root.
819
+
820
+ cpflow 6.0.0 instead ran copies of the actions from the caller repository at
821
+ the triggering revision. See
822
+ [Removing cpflow 6.0.0 action copies](#removing-cpflow-600-action-copies).
823
+
723
824
  ## Version Pins: GitHub Ref vs RubyGems
724
825
 
725
826
  Generated `cpflow-*` workflow files pin `shakacode/control-plane-flow` from
@@ -741,22 +842,22 @@ that input outside the production promotion setup step, regenerate with a newer
741
842
 
742
843
  There are two coordinated inputs, and they protect different things:
743
844
 
744
- - The GitHub ref locks the reusable workflow and the `.cpflow` runtime source
745
- checkout that GitHub runs.
746
- - The installed Ruby gem supplies the generated local composite actions and
747
- wrappers. `CPFLOW_VERSION`, when configured, separately locks the published
748
- `cpflow` runtime installed by the setup action.
845
+ - The GitHub ref locks the reusable workflow, its composite actions, and the
846
+ `.cpflow` runtime source checkout that GitHub runs.
847
+ - The installed Ruby gem supplies the generated wrappers. `CPFLOW_VERSION`,
848
+ when configured, separately locks the published `cpflow` runtime installed by
849
+ the setup action.
749
850
 
750
851
  That means a downstream app cannot rely on the gem alone for GitHub Actions
751
852
  behavior. The safe stable path is still gem-driven for generation, but
752
- developers must commit the generated wrappers and local actions from the gem
753
- that matches the upstream release tag:
853
+ developers must commit the generated wrappers from the gem that matches the
854
+ upstream release tag:
754
855
 
755
856
  1. Publish a `cpflow` gem.
756
857
  2. Install or bundle that released gem in the downstream project.
757
858
  3. Run `cpflow generate-github-actions`.
758
- 4. Commit the generated wrappers and `.github/actions/cpflow-*` files; the
759
- wrappers should point to the matching upstream release tag such as `v5.0.0`.
859
+ 4. Commit the generated wrappers; they should point to the matching upstream
860
+ release tag such as `v5.0.0`.
760
861
 
761
862
  That release tag should point to the same source that produced the RubyGems
762
863
  release. Downstream production automation should use release tags, not `main` or
@@ -765,8 +866,8 @@ feature-branch refs.
765
866
  Generated cross-repository reusable-workflow calls deliberately use an exact
766
867
  release tag such as `v5.0.0`. This is the intentional downstream exception to
767
868
  the repository's full-SHA external-action policy: it keeps the installed gem,
768
- generated wrappers, generated local actions, and upstream workflow on one
769
- reviewed release update contract. It does not extend to external actions inside
869
+ generated wrappers, and upstream workflow on one reviewed release update
870
+ contract. It does not extend to external actions inside
770
871
  those workflows, which remain pinned to full commit SHAs with readable release
771
872
  comments. Unreleased downstream validation temporarily replaces the exact tag
772
873
  with the upstream PR's full commit SHA as described below; moving version
@@ -776,14 +877,14 @@ aliases and branches remain forbidden.
776
877
 
777
878
  Whenever a downstream repo updates the `cpflow` gem, update all checked-in
778
879
  GitHub Actions files in the same PR. The gem version does not make GitHub load
779
- new reusable workflow YAML or local action files by itself; GitHub uses the
780
- workflow ref and `.github/actions/cpflow-*` files committed in the repository.
880
+ new reusable workflow YAML by itself; GitHub uses the workflow ref committed in
881
+ the repository.
781
882
 
782
883
  For a new repository that does not have generated wrappers yet, run
783
884
  `cpflow generate-github-actions` first. Use the update command below for later
784
885
  gem upgrades.
785
886
 
786
- Use the installed gem to refresh the generated workflows and actions:
887
+ Use the installed gem to refresh the generated files:
787
888
 
788
889
  ```sh
789
890
  cpflow update-github-actions
@@ -797,11 +898,111 @@ bundle exec cpflow update-github-actions
797
898
  bin/test-cpflow-github-flow bundle exec cpflow
798
899
  ```
799
900
 
800
- `cpflow update-github-actions` regenerates the workflow wrappers, local
801
- composite actions, and helper files from the installed gem, pins the wrapper
802
- `uses:` refs to `v<gem-version>`, and preserves a single custom staging branch
803
- from the existing generated staging workflow. Pass `--staging-branch BRANCH`
804
- when changing or restoring a custom staging branch explicitly.
901
+ `cpflow update-github-actions` refreshes the generated helpers.
902
+ It preserves every top-level workflow by default, including refs, triggers,
903
+ permissions, and downstream deployment ownership. It does not create staging
904
+ or production deployment workflows when another CI system owns those deploys.
905
+ Select the wrappers you intend to replace explicitly; see
906
+ `cpflow update-github-actions --help` for the current interface:
907
+
908
+ ```sh
909
+ bundle exec cpflow update-github-actions --workflows \
910
+ cpflow-deploy-review-app.yml cpflow-delete-review-app.yml \
911
+ cpflow-cleanup-stale-review-apps.yml cpflow-help-command.yml \
912
+ cpflow-review-app-help.yml
913
+ ```
914
+
915
+ Each selected wrapper is replaced in full with the installed gem's template
916
+ and release ref. Review the diff and reapply downstream changes before
917
+ committing. Unselected wrappers keep their refs: preservation is not evidence
918
+ that old wrappers are compatible with the new release. Migrate wrapper
919
+ contracts and source pins together, then run the validator in the same PR.
920
+ For a selected staging wrapper, a single existing push branch or the default
921
+ main/master pair can be inferred. Missing, malformed, or ambiguous branch
922
+ configuration requires an explicit `--staging-branch BRANCH`; that option also
923
+ requires selecting `cpflow-deploy-staging.yml`.
924
+
925
+ ### Removing cpflow 6.0.0 action copies
926
+
927
+ cpflow 6.0.0 generated `.github/actions/cpflow-*` and ran those copies. Later
928
+ releases load the actions from the pinned ref, so the copies are unused once
929
+ every workflow has moved off 6.0.0:
930
+
931
+ 1. Update the gem, then replace the wrappers and the production promotion
932
+ workflow with `cpflow update-github-actions --workflows FILE...`. Reapply
933
+ downstream customizations.
934
+ 2. Confirm that no workflow is still pinned to cpflow 6.0.0 and that none has a
935
+ `uses: ./.github/actions/cpflow-*` line. In a workflow you maintain by hand,
936
+ change each such line to `uses: ./.cpflow/.github/actions/cpflow-*`.
937
+ 3. Delete the copies with `git rm -r .github/actions/cpflow-*`, delete any
938
+ downstream check that compares them with the gem, and run
939
+ `bin/test-cpflow-github-flow`.
940
+
941
+ `cpflow update-github-actions` lists the copies it finds and never deletes
942
+ them. A wrapper pinned to 6.0.0 runs them without naming them, and a full-SHA
943
+ pin does not say which release it is, so the command cannot prove a copy is
944
+ unused. Until step 3 the copies are harmless: a wrapper pinned to a later
945
+ release never reads them. The validator fails when a workflow names a copy
946
+ that is not checked in.
947
+
948
+ ### Preserving downstream validation
949
+
950
+ The generated `bin/test-cpflow-github-flow` runs baseline checks followed by
951
+ an optional executable `bin/test-cpflow-github-flow-custom`. The extension runs
952
+ from the repository root with the cpflow command argv forwarded unchanged
953
+ (for example, `bundle`, `exec`, `cpflow`), and its nonzero exit fails validation.
954
+ The generator never creates or overwrites the custom extension. Baseline
955
+ production checks apply when the generated production workflow exists;
956
+ downstream-owned production workflows belong in the custom checks.
957
+
958
+ An existing validator that differs from the installed template blocks the
959
+ entire update before any writes. This includes unmodified validators from
960
+ older releases: the updater cannot safely distinguish them from custom code.
961
+ For a legacy installation such as HiChee's 5.3-to-6.0 RC migration:
962
+
963
+ 1. Save the old validator in version control and compare it with its original
964
+ release. Extract the downstream checks into `bin/test-cpflow-github-flow-custom`
965
+ and make it executable. If it is entirely downstream-specific, rename it
966
+ directly. Remove any call back to the generated validator to avoid recursion.
967
+ 2. Remove or rename the old `bin/test-cpflow-github-flow` after preserving those
968
+ checks, then run the update command with the selected wrappers. No override
969
+ flag bypasses this check. Do not use `generate-github-actions --force` as a
970
+ migration shortcut: it intentionally replaces the full generated flow.
971
+ 3. Run the new validator. Confirm that the custom checks execute and that a
972
+ failing custom check still fails the command. Repeating the updater with the
973
+ same selected workflows should produce no further diff.
974
+
975
+ ### Migrating trusted action allowlists
976
+
977
+ Repositories using a closed `trusted_actions` policy must review these exact
978
+ repositories used by the complete generated output:
979
+
980
+ ```yaml
981
+ trusted_actions:
982
+ - actions/checkout
983
+ - docker/setup-buildx-action
984
+ - ruby/setup-ruby
985
+ - shakacode/control-plane-flow
986
+ ```
987
+
988
+ Merge the needed entries into your existing policy rather than replacing it.
989
+ `ruby/setup-ruby` is used by cpflow's setup action, including in
990
+ review-app-only installations. `actions/checkout` and `docker/setup-buildx-action`
991
+ are used directly by the generated production wrapper;
992
+ `shakacode/control-plane-flow` supplies the reusable wrappers. Other existing
993
+ downstream workflows may need additional entries. Upstream reusable workflows
994
+ also use `actions/github-script`; reviewing that upstream implementation remains
995
+ part of trusting `shakacode/control-plane-flow`.
996
+
997
+ Allowlisting does not waive immutable pin requirements. For a policy requiring
998
+ full SHAs, resolve and independently verify the chosen upstream release tag or
999
+ test commit, then run `bin/pin-cpflow-github-ref --version <reviewed-version-tag>
1000
+ <full-sha>`. The helper writes that version/tag beside all cpflow SHA pins and
1001
+ updates obsolete same-line comments. The label is supplied by the caller; the
1002
+ helper does not contact GitHub or certify tag-to-SHA identity. For an unreleased
1003
+ test commit, use its source version tag as context and record the exact commit
1004
+ in the PR. Review the generated diff and run your repository's action security
1005
+ scanner after updating its allowlist.
805
1006
 
806
1007
  When keeping `cpflow` in an app Gemfile, leave a comment next to the gem entry
807
1008
  so future dependency bumps include the wrapper update:
@@ -835,8 +1036,7 @@ action commit, so a moving branch named like `v5.0.0` cannot be used with
835
1036
  runners that cannot reach GitHub should leave `CPFLOW_VERSION` unset and build
836
1037
  `cpflow` from the checked-out ref instead. When testing an unreleased upstream
837
1038
  commit SHA, leave `CPFLOW_VERSION` unset so the workflow builds `cpflow` from the
838
- same source that supplies the reusable workflow. Refresh the generated local
839
- actions from that checkout before testing as described below.
1039
+ same source that supplies the reusable workflow and its actions.
840
1040
 
841
1041
  ## Testing Unreleased Upstream Changes Downstream
842
1042
 
@@ -849,14 +1049,13 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
849
1049
 
850
1050
  ```sh
851
1051
  ruby /path/to/control-plane-flow/bin/cpflow update-github-actions
852
- bin/pin-cpflow-github-ref <upstream-pr-sha>
1052
+ bin/pin-cpflow-github-ref --version <reviewed-version-tag> <upstream-pr-sha>
853
1053
  ```
854
1054
 
855
- The update command copies the PR checkout's canonical composite actions into
856
- `.github/actions/cpflow-*`. The pin helper then updates every generated
1055
+ The pin helper updates every generated
857
1056
  reusable-workflow `uses:` ref plus the production workflow's pinned
858
1057
  `control-plane-flow` checkout and setup validation ref. It accepts release
859
- tags and full commit SHAs by default, rejects branch names such as `main` or
1058
+ tags and full commit SHAs (with a reviewed `--version` label), rejects branch names such as `main` or
860
1059
  `feature/foo`, and requires `--allow-moving-ref` for short-lived local
861
1060
  experiments that should not be committed.
862
1061
 
@@ -881,19 +1080,18 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
881
1080
  matches the repository's installation state.
882
1081
 
883
1082
  For an existing installation, where `cpflow-deploy-review-app.yml` is already
884
- present on the default branch, dispatch the workflow definition and generated
885
- local actions from the unmerged downstream test branch against a suitable open
886
- base-repository PR:
1083
+ present on the default branch, dispatch the workflow definition from the
1084
+ unmerged downstream test branch against a suitable open base-repository PR:
887
1085
 
888
1086
  ```sh
889
1087
  gh workflow run cpflow-deploy-review-app.yml --ref <downstream-test-branch> -f pr_number=<pr-number>
890
1088
  ```
891
1089
 
892
- The `--ref` selects the downstream wrapper and local-action commit under test;
1090
+ The `--ref` selects the downstream wrapper commit under test;
893
1091
  `pr_number` selects the application source to deploy. Do not use a
894
1092
  `+review-app-deploy` comment for this pre-merge validation: `issue_comment`
895
1093
  always loads the workflow definition from the default branch, so it cannot
896
- validate unmerged wrappers or local actions.
1094
+ validate unmerged wrappers.
897
1095
 
898
1096
  For a first installation, GitHub will not dispatch a workflow that does not
899
1097
  yet exist on the default branch, even when `--ref` names the installation
@@ -914,9 +1112,9 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
914
1112
  `bin/pin-cpflow-github-ref vX.Y.Z` only for a ref-only update when the
915
1113
  generated templates are already current.
916
1114
 
917
- For an existing installation, this tests the real reusable workflows, the
918
- unmerged regenerated local composite actions, and the source-built `cpflow` gem
919
- against one immutable upstream commit. A first installation gets the same remote
1115
+ For an existing installation, this tests the real reusable workflows, their
1116
+ composite actions, and the source-built `cpflow` gem against one immutable
1117
+ upstream commit. A first installation gets the same remote
920
1118
  proof immediately after its locally validated files reach the default branch.
921
1119
  Both paths avoid testing against a moving upstream branch.
922
1120
 
@@ -961,7 +1159,7 @@ In practice, porting the flow into a demo app usually follows five phases.
961
1159
 
962
1160
  4. Update `.controlplane/controlplane.yml` with staging, review, and production entries.
963
1161
  5. Confirm that the generated Dockerfile picked a Ruby base image compatible with the app's declared Ruby requirement.
964
- 6. For SQLite-backed apps, confirm that the generated scaffold switched to persistent `db` and `storage` volumes, mounted them into the main workload, and added a release script that runs `rails db:prepare`.
1162
+ 6. For SQLite-backed apps, confirm that the generated scaffold redirects every production database file into persistent `/app/data` (or keeps it under persistent `/app/storage`) without hiding image migrations under `/app/db`, mounts Active Storage at `/app/storage`, and adds a release script that runs `rails db:prepare`.
965
1163
 
966
1164
  **Adapt for the app's runtime:**
967
1165
 
@@ -1005,7 +1203,7 @@ Expand that prompt with app-specific requirements before editing files:
1005
1203
  - make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement
1006
1204
  - preserve repo-defined frontend precompile hooks, such as Shakapacker `precompile_hook` commands or React on Rails `config.auto_load_bundle = true`
1007
1205
  - keep Node available in the final image if Rails or SSR depends on ExecJS, Yarn, or `pnpm` after the main `npm install` layer
1008
- - if `config/database.yml` shows SQLite in production, confirm that `cpflow generate` emitted persistent `db` and `storage` volumes plus a `rails db:prepare` release script; otherwise keep the default Postgres workload
1206
+ - if `config/database.yml` shows SQLite in production, confirm that `cpflow generate` redirected every production database path into persistent `/app/data` or `/app/storage`, emitted both volumes without mounting over `/app/db`, and added a `rails db:prepare` release script; otherwise keep the default Postgres workload
1009
1207
  - inspect the production Dockerfile and package sources for private GitHub dependencies, and wire `DOCKER_BUILD_SSH_KEY` plus `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the build uses `RUN --mount=type=ssh` against non-GitHub hosts
1010
1208
  - add extra `app_workloads` and template files for any runtime sidecars, workers, or renderer processes
1011
1209
  - make sure any sidecar process exposed to sibling workloads binds to `0.0.0.0` instead of container-local `localhost`
data/docs/commands.md CHANGED
@@ -30,6 +30,7 @@ cpflow ai-github-flow-prompt
30
30
  - Picks templates from the `.controlplane/templates` directory
31
31
  - Templates are ordinary Control Plane templates but with variable preprocessing
32
32
  - Use `--preserve-existing-runtime` to retain each workload container's configured app image, even when the workload is unready, and skip existing secret resources entirely while applying other template changes
33
+ - Use `--exclude-secret-template NAME` and `--exclude-policy-template NAME` to exclude exact named templates without changing workload image handling
33
34
  - Missing or invalid workload images use only an unambiguous app image from ready workloads; refresh fails before applying templates when no safe fallback exists
34
35
 
35
36
  **Preprocessed template variables:**
@@ -135,6 +136,7 @@ cpflow copy-image-from-upstream -a $APP_NAME --upstream-token $UPSTREAM_TOKEN --
135
136
  - Deletes the whole app (GVC with all workloads, all volumesets and all images) or a specific workload
136
137
  - Also unbinds the app from the secrets policy and any configured `shared_secret_grants` policies, as long as both the identity and each policy exist (and are bound)
137
138
  - For the app-specific secrets policy, removes every permission held by the app identity; for `shared_secret_grants`, removes only `reveal`
139
+ - Removes a marked per-app dictionary and exact-target unbound policy for generated review credentials, including after the opt-in is removed from a dynamically matched review-app entry
138
140
  - Will ask for explicit user confirmation
139
141
  - Runs a pre-deletion hook before the app is deleted if `hooks.pre_deletion` is specified in the `.controlplane/controlplane.yml` file
140
142
  - If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
@@ -217,7 +219,7 @@ Creates base Control Plane config and template files for a Rails project:
217
219
  - infers the app prefix from the current directory and wires staging, review, and production entries
218
220
  - infers the Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's `Gemfile`
219
221
  - preserves repo-defined asset precompile hooks, including React on Rails auto bundle generation
220
- - detects SQLite in `config/database.yml` and generates persistent `db` and `storage` volume templates instead of the default Postgres workload
222
+ - detects SQLite in `config/database.yml` and generates persistent `/app/data` and `/app/storage` volume templates without hiding image migrations under `/app/db`
221
223
 
222
224
  ```sh
223
225
  # Creates .controlplane directory with Control Plane config and starter templates
@@ -232,8 +234,8 @@ Creates GitHub Actions templates for a Heroku Flow style Control Plane pipeline:
232
234
  - manual promotion from staging to production
233
235
  - nightly cleanup and PR help workflows
234
236
 
235
- It also copies cpflow's composite actions into `.github/actions/cpflow-*`
236
- so every local `uses:` target is checked in and can be audited directly.
237
+ The workflows load cpflow's composite actions from this gem's repository at
238
+ the ref each workflow pins, so no `.github/actions/cpflow-*` copies are generated.
237
239
 
238
240
  Pass `--staging-branch BRANCH` when staging should auto-deploy from a branch
239
241
  other than `main` or `master`; the generator will bake that branch into the
@@ -243,7 +245,7 @@ Pass `--force` to overwrite existing generated files. Prefer
243
245
  repo.
244
246
 
245
247
  ```sh
246
- # Creates workflow wrappers, local composite actions, and validation helpers
248
+ # Creates workflow wrappers and validation helpers
247
249
  cpflow generate-github-actions
248
250
 
249
251
  # Creates the flow with staging deploys triggered from develop
@@ -558,13 +560,14 @@ cpflow run -a $APP_NAME --entrypoint /app/alternative-entrypoint.sh -- rails db:
558
560
  - Configures app to have org-level secrets with default name `"{APP_PREFIX}-secrets"`
559
561
  using org-level policy with default name `"{APP_PREFIX}-secrets-policy"` (names can be customized, see docs)
560
562
  - Creates identity for secrets if it does not exist
563
+ - For dynamically named review apps with `generated_review_secret_keys`, checks an existing policy before writing credentials, creates a per-app dictionary, skips its secret template during initial setup, and fills missing disposable keys without printing or rotating values
561
564
  - Binds the app identity to any configured `shared_secret_grants` policies as part of the secrets setup flow; skipped when `--skip-secrets-setup` or `--skip-secret-access-binding` is provided, or `skip_secrets_setup` is set
562
565
  - Use `--skip-secrets-setup` to prevent the automatic setup of secrets,
563
566
  or set it through `skip_secrets_setup` in the `.controlplane/controlplane.yml` file
564
567
  - Runs a post-creation hook after the app is created if `hooks.post_creation` is specified in the `.controlplane/controlplane.yml` file
565
568
  - If the hook exits with a non-zero code, the command will stop executing and also exit with a non-zero code
566
569
  - Use `--skip-post-creation-hook` to skip the hook if specified in `controlplane.yml`
567
- - Use `--refresh-templates` to apply configured templates noninteractively to an existing app while preserving each workload's configured app image even when workloads are unready or use mixed image versions, skipping existing secret resources entirely, repairing secrets access bindings, and skipping the post-creation hook
570
+ - Use `--refresh-templates` to apply configured templates noninteractively to an existing app while preserving each workload's configured app image even when workloads are unready or use mixed image versions, skipping existing secret templates (but filling missing opt-in generated review keys), repairing secrets access bindings, and skipping the post-creation hook
568
571
 
569
572
  ```sh
570
573
  cpflow setup-app -a $APP_NAME
@@ -588,25 +591,38 @@ cpflow terraform import
588
591
 
589
592
  ### `update-github-actions`
590
593
 
591
- Regenerates the cpflow workflow wrappers, local composite actions, and
592
- helper files from the currently installed cpflow gem. Use this after
593
- updating the cpflow gem so checked-in workflow wrappers move to the
594
- matching upstream release tag, for example `v5.3.0`, and the
595
- generated action implementations move with them.
594
+ Refreshes helper files from the installed gem. Workflows load cpflow's
595
+ composite actions from the pinned cpflow ref, so the command reports any
596
+ `.github/actions/cpflow-*` copies left by cpflow 6.0.0 for you to delete
597
+ once no workflow is pinned to 6.0.0. It never deletes them itself.
598
+ All top-level workflows are preserved by default, including their refs,
599
+ triggers, permissions, and deployment ownership. Use --workflows FILE...
600
+ to explicitly add or replace named generated workflows. Replacement resets
601
+ each selected workflow to its template and matching v6.0.1 ref;
602
+ review and reapply any downstream customizations in the same PR.
596
603
 
597
- If the existing generated staging workflow uses a custom single staging
598
- branch, the command preserves it. Pass `--staging-branch BRANCH` to set or
599
- replace the generated staging branch explicitly.
604
+ Selecting cpflow-deploy-staging.yml preserves a single existing push branch
605
+ or the default main/master pair. Missing or ambiguous branches require
606
+ --staging-branch BRANCH. This option requires selecting the staging workflow.
607
+
608
+ A differing existing bin/test-cpflow-github-flow blocks all writes. Move
609
+ downstream checks to executable bin/test-cpflow-github-flow-custom and
610
+ remove or rename the old validator before updating. The generated validator
611
+ invokes that extension after its baseline checks; the extension is never
612
+ generated or overwritten. See docs/ci-automation.md for migration guidance.
600
613
 
601
614
  ```sh
602
- # After updating the cpflow gem, refresh every generated GitHub Actions file
615
+ # Refresh helpers while preserving downstream workflows
603
616
  cpflow update-github-actions
604
617
 
605
618
  # When running cpflow through Bundler
606
619
  bundle exec cpflow update-github-actions
607
620
 
608
- # Preserve or set a custom staging branch
609
- cpflow update-github-actions --staging-branch develop
621
+ # Explicitly replace the review-app wrapper
622
+ cpflow update-github-actions --workflows cpflow-deploy-review-app.yml
623
+
624
+ # Explicitly add or replace staging deployment from develop
625
+ cpflow update-github-actions --workflows cpflow-deploy-staging.yml --staging-branch develop
610
626
  ```
611
627
 
612
628
  ### `version`