cpflow 6.0.0.rc.0 → 6.0.0

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 (60) 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-deploy-review-app.yml +13 -1
  13. data/.github/workflows/cpflow-deploy-staging.yml +12 -1
  14. data/.github/workflows/cpflow-promote-staging-to-production.yml +128 -9
  15. data/.github/workflows/rspec.yml +24 -0
  16. data/AGENTS.md +18 -3
  17. data/CHANGELOG.md +18 -3
  18. data/CONTRIBUTING.md +1 -1
  19. data/Gemfile.lock +1 -1
  20. data/README.md +5 -2
  21. data/docs/ai-github-flow-prompt.md +2 -2
  22. data/docs/ci-automation.md +178 -14
  23. data/docs/commands.md +26 -13
  24. data/docs/postgres-review-apps.md +90 -0
  25. data/docs/postgres.md +233 -375
  26. data/docs/secrets-and-env-values.md +42 -0
  27. data/docs/sidebars.ts +1 -0
  28. data/lib/command/ai_github_flow_prompt.rb +2 -2
  29. data/lib/command/apply_template.rb +36 -4
  30. data/lib/command/base.rb +56 -3
  31. data/lib/command/delete.rb +174 -4
  32. data/lib/command/deploy_image.rb +6 -0
  33. data/lib/command/generate.rb +247 -3
  34. data/lib/command/generate_github_actions.rb +7 -1
  35. data/lib/command/setup_app.rb +92 -10
  36. data/lib/command/update_github_actions.rb +81 -43
  37. data/lib/core/config.rb +73 -0
  38. data/lib/core/controlplane.rb +22 -2
  39. data/lib/core/controlplane_api.rb +28 -2
  40. data/lib/core/controlplane_api_direct.rb +17 -0
  41. data/lib/core/disposable_postgres_helpers.rb +167 -0
  42. data/lib/core/doctor_service.rb +7 -0
  43. data/lib/core/github_flow_readiness/checks.rb +2 -1
  44. data/lib/core/github_flow_readiness_service.rb +4 -0
  45. data/lib/core/repo_introspection.rb +112 -2
  46. data/lib/cpflow/version.rb +1 -1
  47. data/lib/generator_templates/.dockerignore +16 -0
  48. data/lib/generator_templates/Dockerfile +9 -5
  49. data/lib/generator_templates/entrypoint.sh +2 -0
  50. data/lib/generator_templates/templates/postgres.yml +9 -0
  51. data/lib/generator_templates_sqlite/controlplane.yml +2 -1
  52. data/lib/generator_templates_sqlite/release_script.sh +1 -1
  53. data/lib/generator_templates_sqlite/templates/rails.yml +2 -1
  54. data/lib/github_flow_templates/.github/cpflow-help.md +39 -12
  55. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-review-app.yml +1 -0
  56. data/lib/github_flow_templates/.github/workflows/cpflow-deploy-staging.yml +1 -0
  57. data/lib/github_flow_templates/.github/workflows/cpflow-promote-staging-to-production.yml +128 -9
  58. data/lib/github_flow_templates/bin/pin-cpflow-github-ref +37 -13
  59. data/lib/github_flow_templates/bin/test-cpflow-github-flow +62 -43
  60. metadata +11 -3
@@ -77,7 +77,7 @@ The second command writes namespaced files so they can coexist with an app's exi
77
77
  Docker base Ruby version from `.ruby-version`, `.tool-versions`, or the app's
78
78
  `Gemfile`, preserves repo-defined frontend precompile hooks such as Shakapacker
79
79
  `precompile_hook` commands or React on Rails auto bundle generation, and
80
- switches to persistent SQLite `db` and `storage` templates when
80
+ switches to persistent SQLite data and Active Storage templates when
81
81
  `config/database.yml` shows SQLite in production.
82
82
 
83
83
  `cpflow github-flow-readiness` checks public RubyGems and npm registry metadata
@@ -230,14 +230,75 @@ because they could change the target org name after normalization.
230
230
 
231
231
  Production promotion copies the exact image currently deployed on the selected
232
232
  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
233
+ the source copy while the production tag is derived from the tag portion. Staging tags must
234
+ include a full lowercase 40-character Git commit SHA suffix (`_<commit>`),
235
+ which is preserved in the new production tag. A staging image without that
236
+ provenance stops promotion before approval. The copy step uses
236
237
  `docker buildx imagetools create --prefer-index=false --tag` with isolated
237
238
  Docker credentials, which preserves multi-architecture manifests, preserves
238
239
  single-platform manifest format when supported, and avoids pulling image layers
239
240
  onto the GitHub Actions runner.
240
241
 
242
+ When you start production promotion, the **preview-promotion** job finishes
243
+ before GitHub requests approval for the protected production Environment. Open
244
+ the run summary’s **Commits ready for production** section before approving. It
245
+ compares staging’s deployed primary-workload image with the last recorded
246
+ successful `production-*` GitHub release. It links the full comparison, lists up
247
+ to 100 commit subjects, and flags diverged history or staging behind production.
248
+ Matching SHAs report no commits to promote. Release discovery checks the latest
249
+ 100 releases; missing production history or GitHub API failures produce an
250
+ explicit availability notice. The preview uses the existing staging token and
251
+ GitHub `contents: read` permission; it has no production token.
252
+
253
+ The preview selects the primary workload from the staging app’s
254
+ `app_workloads` configuration. `PRODUCTION_APP_NAME` and
255
+ `CPLN_ORG_PRODUCTION` can remain production Environment variables. When staging
256
+ and production have different primary-workload defaults, set `PRIMARY_WORKLOAD`
257
+ as a repository variable shared by both jobs; the protected job rejects a
258
+ workload-selection mismatch. A Control Plane read failure or invalid staging
259
+ SHA fails the preview and prevents approval from starting.
260
+
261
+ After approval, the protected job checks that staging still uses the captured
262
+ image and, when a release baseline was available, that live production matches
263
+ that release’s commit. A mismatch stops before copying or deploying an image;
264
+ rerun after staging changes, or reconcile the production release record after an
265
+ out-of-band production deployment. If production history was unavailable, the
266
+ summary does not claim to know the production-to-staging diff.
267
+
268
+ Existing repositories adopt this with
269
+ `cpflow update-github-actions --workflows cpflow-promote-staging-to-production.yml`.
270
+ Review and reapply downstream workflow customizations before committing the
271
+ replacement and the refreshed local composite actions. The bare update command
272
+ preserves existing top-level workflows.
273
+
274
+ ### Recover a missing or stale production release record
275
+
276
+ A successful production deployment and its GitHub release are separate jobs.
277
+ If deployment succeeded but **create-github-release** failed, open that original
278
+ Actions run and choose **Re-run failed jobs**. This retries release publication
279
+ at the original promoted SHA; do not rerun the whole promotion merely to repair
280
+ the record. Confirm the resulting `production-*` release targets the live
281
+ production image’s SHA, then start a fresh promotion preview.
282
+
283
+ After an out-of-band production deploy or rollback, an app maintainer should
284
+ first verify the healthy live primary-workload image and its full commit suffix
285
+ with `cpln workload get <primary-workload> --gvc <production-app> --org
286
+ <production-org> -o json`. Use that SHA to publish a fresh release record; do not
287
+ move an existing release tag. For example, after replacing `<verified-live-sha>`:
288
+
289
+ ```sh
290
+ gh release create "production-$(date -u +%Y-%m-%d-%H%M%S)-0" \
291
+ --target <verified-live-sha> \
292
+ --title "Production state reconciliation" \
293
+ --notes "Records the verified live production commit after a manual deployment or rollback."
294
+ ```
295
+
296
+ Run this in the app repository with the maintainer’s normal GitHub credentials.
297
+ Verify the new tag’s commit and the live image agree, then rerun promotion to
298
+ produce a fresh preview. If the live image has no commit suffix, establish its
299
+ provenance and use a traceable image before recording a baseline; guessing a
300
+ branch head would make the comparison misleading.
301
+
241
302
  Before copying the image, production promotion compares the environment variable
242
303
  names exposed by staging and production at both the GVC level and each configured
243
304
  app workload's container level. Variables present in staging are treated as
@@ -425,9 +486,11 @@ Recommended org layout:
425
486
  review/staging CI operations; use a dedicated review-app org only if you also customize the generated
426
487
  workflow/configuration to target that org separately
427
488
 
428
- Optional repository secret for private dependency builds:
489
+ Optional repository secrets for Docker builds:
429
490
 
430
491
  - `DOCKER_BUILD_SSH_KEY`: private SSH key used when the Dockerfile needs `RUN --mount=type=ssh` to fetch private GitHub dependencies during image build
492
+ - `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.
493
+ - `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
494
 
432
495
  Optional repository variables for private dependency builds:
433
496
 
@@ -570,6 +633,30 @@ The generated `cpflow-build-docker-image` action supports this without hardcodin
570
633
  - set `DOCKER_BUILD_SSH_KEY` if the Docker build needs SSH access to GitHub
571
634
  - optionally set `DOCKER_BUILD_SSH_KNOWN_HOSTS` when the SSH build host is not GitHub.com or you need custom host entries
572
635
  - set `DOCKER_BUILD_EXTRA_ARGS` when you need extra `docker build` flags
636
+ - set `DOCKER_BUILD_SECRETS` for staging builds that need credentials such as a private package token or a source-map upload token
637
+ - set `REVIEW_APP_DOCKER_BUILD_SECRETS` separately if review-app builds need credentials
638
+
639
+ For example, store the following as the **value** of the `DOCKER_BUILD_SECRETS` GitHub Actions secret:
640
+
641
+ ```text
642
+ sentry_auth_token=<source-map upload token>
643
+ npm_token=<private package token>
644
+ ```
645
+
646
+ 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`.
647
+
648
+ A Dockerfile can consume a token only for the command that needs it:
649
+
650
+ ```dockerfile
651
+ # syntax=docker/dockerfile:1
652
+ ARG SENTRY_UPLOAD=false
653
+ RUN --mount=type=secret,id=sentry_auth_token,env=SENTRY_AUTH_TOKEN \
654
+ SENTRY_UPLOAD=${SENTRY_UPLOAD} npm run build
655
+ ```
656
+
657
+ 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.
658
+
659
+ 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
660
 
574
661
  For example, a repo that installs private dependencies from GitHub during Docker build can set:
575
662
 
@@ -797,11 +884,88 @@ bundle exec cpflow update-github-actions
797
884
  bin/test-cpflow-github-flow bundle exec cpflow
798
885
  ```
799
886
 
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.
887
+ `cpflow update-github-actions` refreshes local composite actions and helpers.
888
+ It preserves every top-level workflow by default, including refs, triggers,
889
+ permissions, and downstream deployment ownership. It does not create staging
890
+ or production deployment workflows when another CI system owns those deploys.
891
+ Select the wrappers you intend to replace explicitly; see
892
+ `cpflow update-github-actions --help` for the current interface:
893
+
894
+ ```sh
895
+ bundle exec cpflow update-github-actions --workflows \
896
+ cpflow-deploy-review-app.yml cpflow-delete-review-app.yml \
897
+ cpflow-cleanup-stale-review-apps.yml cpflow-help-command.yml \
898
+ cpflow-review-app-help.yml
899
+ ```
900
+
901
+ Each selected wrapper is replaced in full with the installed gem's template
902
+ and release ref. Review the diff and reapply downstream changes before
903
+ committing. Unselected wrappers keep their refs: preservation is not evidence
904
+ that old wrappers are compatible with new local actions. Migrate wrapper
905
+ contracts and source pins together, then run the validator in the same PR.
906
+ For a selected staging wrapper, a single existing push branch or the default
907
+ main/master pair can be inferred. Missing, malformed, or ambiguous branch
908
+ configuration requires an explicit `--staging-branch BRANCH`; that option also
909
+ requires selecting `cpflow-deploy-staging.yml`.
910
+
911
+ ### Preserving downstream validation
912
+
913
+ The generated `bin/test-cpflow-github-flow` runs baseline checks followed by
914
+ an optional executable `bin/test-cpflow-github-flow-custom`. The extension runs
915
+ from the repository root with the cpflow command argv forwarded unchanged
916
+ (for example, `bundle`, `exec`, `cpflow`), and its nonzero exit fails validation.
917
+ The generator never creates or overwrites the custom extension. Baseline
918
+ production checks apply when the generated production workflow exists;
919
+ downstream-owned production workflows belong in the custom checks.
920
+
921
+ An existing validator that differs from the installed template blocks the
922
+ entire update before any writes. This includes unmodified validators from
923
+ older releases: the updater cannot safely distinguish them from custom code.
924
+ For a legacy installation such as HiChee's 5.3-to-6.0 RC migration:
925
+
926
+ 1. Save the old validator in version control and compare it with its original
927
+ release. Extract the downstream checks into `bin/test-cpflow-github-flow-custom`
928
+ and make it executable. If it is entirely downstream-specific, rename it
929
+ directly. Remove any call back to the generated validator to avoid recursion.
930
+ 2. Remove or rename the old `bin/test-cpflow-github-flow` after preserving those
931
+ checks, then run the update command with the selected wrappers. No override
932
+ flag bypasses this check. Do not use `generate-github-actions --force` as a
933
+ migration shortcut: it intentionally replaces the full generated flow.
934
+ 3. Run the new validator. Confirm that the custom checks execute and that a
935
+ failing custom check still fails the command. Repeating the updater with the
936
+ same selected workflows should produce no further diff.
937
+
938
+ ### Migrating trusted action allowlists
939
+
940
+ Repositories using a closed `trusted_actions` policy must review these exact
941
+ repositories used by the complete generated output:
942
+
943
+ ```yaml
944
+ trusted_actions:
945
+ - actions/checkout
946
+ - docker/setup-buildx-action
947
+ - ruby/setup-ruby
948
+ - shakacode/control-plane-flow
949
+ ```
950
+
951
+ Merge the needed entries into your existing policy rather than replacing it.
952
+ `ruby/setup-ruby` is used by the generated local setup action, including in
953
+ review-app-only installations. `actions/checkout` and `docker/setup-buildx-action`
954
+ are used directly by the generated production wrapper;
955
+ `shakacode/control-plane-flow` supplies the reusable wrappers. Other existing
956
+ downstream workflows may need additional entries. Upstream reusable workflows
957
+ also use `actions/github-script`; reviewing that upstream implementation remains
958
+ part of trusting `shakacode/control-plane-flow`.
959
+
960
+ Allowlisting does not waive immutable pin requirements. For a policy requiring
961
+ full SHAs, resolve and independently verify the chosen upstream release tag or
962
+ test commit, then run `bin/pin-cpflow-github-ref --version <reviewed-version-tag>
963
+ <full-sha>`. The helper writes that version/tag beside all cpflow SHA pins and
964
+ updates obsolete same-line comments. The label is supplied by the caller; the
965
+ helper does not contact GitHub or certify tag-to-SHA identity. For an unreleased
966
+ test commit, use its source version tag as context and record the exact commit
967
+ in the PR. Review the generated diff and run your repository's action security
968
+ scanner after updating its allowlist.
805
969
 
806
970
  When keeping `cpflow` in an app Gemfile, leave a comment next to the gem entry
807
971
  so future dependency bumps include the wrapper update:
@@ -849,14 +1013,14 @@ releasing it. Use an immutable commit SHA from the upstream PR branch:
849
1013
 
850
1014
  ```sh
851
1015
  ruby /path/to/control-plane-flow/bin/cpflow update-github-actions
852
- bin/pin-cpflow-github-ref <upstream-pr-sha>
1016
+ bin/pin-cpflow-github-ref --version <reviewed-version-tag> <upstream-pr-sha>
853
1017
  ```
854
1018
 
855
1019
  The update command copies the PR checkout's canonical composite actions into
856
1020
  `.github/actions/cpflow-*`. The pin helper then updates every generated
857
1021
  reusable-workflow `uses:` ref plus the production workflow's pinned
858
1022
  `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
1023
+ tags and full commit SHAs (with a reviewed `--version` label), rejects branch names such as `main` or
860
1024
  `feature/foo`, and requires `--allow-moving-ref` for short-lived local
861
1025
  experiments that should not be committed.
862
1026
 
@@ -961,7 +1125,7 @@ In practice, porting the flow into a demo app usually follows five phases.
961
1125
 
962
1126
  4. Update `.controlplane/controlplane.yml` with staging, review, and production entries.
963
1127
  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`.
1128
+ 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
1129
 
966
1130
  **Adapt for the app's runtime:**
967
1131
 
@@ -1005,7 +1169,7 @@ Expand that prompt with app-specific requirements before editing files:
1005
1169
  - make sure the generated Dockerfile uses a Ruby base image compatible with the app's declared Ruby requirement
1006
1170
  - preserve repo-defined frontend precompile hooks, such as Shakapacker `precompile_hook` commands or React on Rails `config.auto_load_bundle = true`
1007
1171
  - 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
1172
+ - 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
1173
  - 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
1174
  - add extra `app_workloads` and template files for any runtime sidecars, workers, or renderer processes
1011
1175
  - 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
@@ -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,35 @@ 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 local composite actions and helper files from the installed gem.
595
+ All top-level workflows are preserved by default, including their refs,
596
+ triggers, permissions, and deployment ownership. Use --workflows FILE...
597
+ to explicitly add or replace named generated workflows. Replacement resets
598
+ each selected workflow to its template and matching v6.0.0 ref;
599
+ review and reapply any downstream customizations in the same PR.
596
600
 
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.
601
+ Selecting cpflow-deploy-staging.yml preserves a single existing push branch
602
+ or the default main/master pair. Missing or ambiguous branches require
603
+ --staging-branch BRANCH. This option requires selecting the staging workflow.
604
+
605
+ A differing existing bin/test-cpflow-github-flow blocks all writes. Move
606
+ downstream checks to executable bin/test-cpflow-github-flow-custom and
607
+ remove or rename the old validator before updating. The generated validator
608
+ invokes that extension after its baseline checks; the extension is never
609
+ generated or overwritten. See docs/ci-automation.md for migration guidance.
600
610
 
601
611
  ```sh
602
- # After updating the cpflow gem, refresh every generated GitHub Actions file
612
+ # Refresh actions/helpers while preserving downstream workflows
603
613
  cpflow update-github-actions
604
614
 
605
615
  # When running cpflow through Bundler
606
616
  bundle exec cpflow update-github-actions
607
617
 
608
- # Preserve or set a custom staging branch
609
- cpflow update-github-actions --staging-branch develop
618
+ # Explicitly replace the review-app wrapper
619
+ cpflow update-github-actions --workflows cpflow-deploy-review-app.yml
620
+
621
+ # Explicitly add or replace staging deployment from develop
622
+ cpflow update-github-actions --workflows cpflow-deploy-staging.yml --staging-branch develop
610
623
  ```
611
624
 
612
625
  ### `version`
@@ -0,0 +1,90 @@
1
+ # PostgreSQL for disposable review apps
2
+
3
+ Use the generated `.controlplane/templates/postgres.yml` for a PostgreSQL workload
4
+ that belongs to one disposable review app. For production database migration, see
5
+ [Migrating PostgreSQL from Heroku to RDS](./postgres.md).
6
+
7
+ ## Enable cleanup for new review apps
8
+
9
+ The generated template marks three helper resources:
10
+
11
+ | Resource | Purpose |
12
+ | --- | --- |
13
+ | `<APP>-pg` | Credential dictionary |
14
+ | `<APP>-pg-script` | Initialization-script secret |
15
+ | `<APP>-pg-access` | Policy granting access to those secrets |
16
+
17
+ Each helper template carries this tag:
18
+
19
+ ```yaml
20
+ tags:
21
+ cpflow-disposable-postgres-app: "{{APP_NAME}}"
22
+ ```
23
+
24
+ `cpflow apply-template` retains the tag for dynamically named review apps and removes
25
+ it for persistent apps. In an existing project, add the tag to all three helper
26
+ **templates before creating new review apps**. Reapplying a marked template to an
27
+ existing unmarked resource is refused; it does not adopt that resource.
28
+
29
+ Before opting in, check that your deployment token can:
30
+
31
+ - Read and delete the three helper resources.
32
+ - Read each secret's effective access report (`viewAccessReport` on the org).
33
+
34
+ A missing or incomplete access report blocks cleanup before app data is deleted.
35
+
36
+ ## Delete an app
37
+
38
+ Run the normal app deletion command:
39
+
40
+ ```sh
41
+ cpflow delete -a APP_NAME
42
+ ```
43
+
44
+ Cleanup validates the helpers before deleting app data. It removes the marked
45
+ policy and secrets before deleting the GVC. Deleting a single workload with
46
+ `cpflow delete -a APP_NAME -w WORKLOAD_NAME` preserves the helpers.
47
+
48
+ The ownership checks require:
49
+
50
+ - The same app marker on every surviving helper.
51
+ - A policy targeting exactly the two helper secrets.
52
+ - Only `reveal` grants in that policy, for the app and PostgreSQL identities in the
53
+ app's GVC.
54
+ - No helper listed in `shared_secret_grants`.
55
+ - No effective secret access through another application policy or identity.
56
+
57
+ Reapplying marked helpers with `cpflow apply-template` uses the same checks.
58
+
59
+ ## Understand a cleanup refusal
60
+
61
+ Cleanup preserves resources when a marker, policy, or access report cannot establish
62
+ exclusive ownership. A refusal aborts whole-app deletion before live app data is
63
+ deleted. Fix the reported condition before retrying; do not remove a
64
+ sharing grant until you know which applications depend on it.
65
+
66
+ | Condition | Result |
67
+ | --- | --- |
68
+ | All helpers are unmarked legacy resources | App deletion proceeds; helpers are preserved |
69
+ | Marked and unmarked helpers coexist | Cleanup refuses deletion |
70
+ | A marker names another app | Cleanup refuses deletion |
71
+ | Policy targets or principals differ from the expected app scope | Cleanup refuses deletion |
72
+ | A helper is configured as shared | Cleanup refuses deletion |
73
+ | Secret access reports are unavailable or incomplete | Cleanup refuses deletion |
74
+
75
+ The access checks include permissions implied by `edit` or `manage`. Global `manage`
76
+ grants to org groups, users, or service accounts count as org administration.
77
+ Targeted or query grants, and grants to other GVC identities, block cleanup.
78
+
79
+ For shared staging credentials, use the
80
+ [shared-secret configuration guide](./secrets-and-env-values.md#shared-secrets-for-review-apps)
81
+ instead of marking them as disposable helpers.
82
+
83
+ ## Recover after interruption
84
+
85
+ Repeat `cpflow delete` with the same app name. Cleanup tolerates an already absent
86
+ GVC, policy, or secret and rechecks the surviving helpers before deleting them.
87
+
88
+ Unmarked legacy helpers need manual cleanup after you establish that no other app
89
+ uses them. Rolling back to an older cpflow version leaves the tags in place but
90
+ stops automatic helper cleanup.